テトたちのにっきちょう

テト・ペンタ・ヘキサの3人組によるゆるふわ系ブログ

MENU

【ソースコード】Google Driveのフォルダ内ファイルが編集されたらSlackに通知を送るプログラムを作ってみた【GAS】

スポンサーリンク

 こんにちは。テトです。

 最近、チームで作業するときに簡単なファイル共有としてGoogle Driveを使うことがあるのですが、共有フォルダの中のファイルを複数人が編集(新規追加・更新・削除)し合う時に通知が欲しいなと思うようになりました。

 他の人が更新したのが分からないまま古いバージョンを編集して更新してしまう危険があったので……

 リアルなら口頭で一言いえば済みますが、リモートで頻繁に編集し合うと何回も伝えるのが面倒っちい!

 ということで、そういうツールを作ってみました。

 参考にさせて頂いたサイトは下部に紹介しております。

Google Driveの通知機能ではダメなの?

 Google Driveにも通知機能はあるんですが、あくまでファイルを1つ1つ共有された時に通知が来るようで、フォルダ自体を共有してその中でファイルをやり取りする運用方法では通知は来ないようです。

support.google.com

想定される使い方

  • Google Driveにてフォルダ自体をチームと共有していて、フォルダ内のファイルを編集した際に通知が欲しい
  • 同様の使い方で、誤削除→紛失を防ぐために削除されたら通知が欲しい


※写真などファイルの数が多いと一気に追加しただけで鬼のように通知が来ると思うのでそれは想定していません。あくまでよく使うドキュメントなどの編集履歴の通知目的です。
※共有フォルダのオーナーは自分じゃないとエラーが出て動きません。
※IFTTTを使うので、無料枠を使い切っている方は注意。

作り方

Googleスプレッドシートを作成する

 Google Driveの適当なところにGoogleスプレッドシートを作成します。この時、通知対象のフォルダに入れてしまうと更新→通知→スプレッドシート更新→通知の無限ループになってしまうので、別のところに作成しましょう。

 作成したら、シート1とシート2にそれぞれヘッダを記入します。

f:id:tetragon64:20210722164125p:plain f:id:tetragon64:20210722164206p:plain

GAS入力

 スプレッドシートが準備できたら、いよいよGAS(Google Apps Script)入力です。「ツール」-「スクリプトエディタ」で開きます。

f:id:tetragon64:20210722164219p:plain

Googleアカウントを複数持っている場合、今ログインしているアカウントがデフォルトアカウントでないとここで弾かれてしまうようです。その場合、一度すべてログアウトするのは面倒なので、シークレットモードでログインするとデフォルトアカウントになるので入れます。

 開けたら、以下のコードを貼付してください。

//対象とするGoogleDriveフォルダのID ブラウザでアクセスしてURL見れば分かる
var FOLDER_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
//更新日時を記録するスプレッドシートのID ブラウザでアクセスしてURL見れば分かる
var SHEET_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
//編集履歴が入力されるスプレッドシートのシート名(下に表示されるタブのやつ)
var UPDATE_SHEET_NAME = "シート1";
//直前のフォルダ内ファイルのリストが入っているシート名
var LIST_SHEET_NAME = "シート2";


//初めて使う時、最初に実行すること。シート2にリストが作成されます
function createList() {
  var spreadsheet = SpreadsheetApp.openById(SHEET_ID);
  var listSheet = spreadsheet.getSheetByName(LIST_SHEET_NAME);

  listSheet.deleteRows(2, listSheet.getMaxRows()-1);

  var folder = DriveApp.getFolderById(FOLDER_ID);
  var filesList = getAllFiles(folder);
  
  for (key in filesList) {
    var rowno = listSheet.getLastRow() + 1
    listSheet.getRange(rowno, 1).setValue(key);
    listSheet.getRange(rowno, 2).setValue(filesList[key].name);
    listSheet.getRange(rowno, 3).setValue(filesList[key].lastUpdate);
    listSheet.getRange(rowno, 4).setValue(filesList[key].editor);
    listSheet.getRange(rowno, 5).setValue(filesList[key].url);
  }
}


//フォルダ内を再帰的に探索してすべてのファイルを連想配列にして返す
function getAllFiles(targetFolder) {
  var filesList = {};

  var files = targetFolder.getFiles();
  while (files.hasNext()) {
    
    var fileobj = files.next();
    filesList[fileobj.getId()] = {
      name: fileobj.getName(),
      lastUpdate: fileobj.getLastUpdated(),
      editor: Drive.Files.get(fileobj.getId()).lastModifyingUserName,
      url: fileobj.getUrl()
    };
    
  }

  var child_folders = targetFolder.getFolders();
  while (child_folders.hasNext()) {

    var child_folder = child_folders.next();
    var filesList2 = getAllFiles(child_folder);
    for (var key in filesList2) {
      filesList[key] = filesList2[key];
    }
  }
  return filesList;
}


//シート2のリストと最新取得してきたファイルのリストを比較して、差分をシート1に出力
function updateCheck() {
  var folder = DriveApp.getFolderById(FOLDER_ID);
  //共有フォルダ内の最新状態のファイルリストを取得
  var currentFilesList = getAllFiles(folder);

  var spreadsheet = SpreadsheetApp.openById(SHEET_ID);
  var listSheet = spreadsheet.getSheetByName(LIST_SHEET_NAME);

  
  var filesList = {};
  //シート2のリストを取得
  for (var i=1; i<listSheet.getMaxRows(); i++) {
    filesList[listSheet.getRange(i+1, 1).getValue()] = {
      name: listSheet.getRange(i+1, 2).getValue(),
      lastUpdate: listSheet.getRange(i+1, 3).getValue()
    }
  }

  var updateList = {};

  for(key in currentFilesList) {
    //もし同じIDのファイルがあって、更新日時が新しくなっているなら更新されている
    if(key in filesList) {
      if(currentFilesList[key].lastUpdate.getTime() > filesList[key].lastUpdate.getTime()) {
        updateList[key] = {
          name: currentFilesList[key].name,
          lastUpdate: currentFilesList[key].lastUpdate,
          editor: currentFilesList[key].editor,
          url: currentFilesList[key].url,
          type: "更新"
        }
      }
    //同じIDのファイルがシート2にないなら新規追加されている
    } else {
      updateList[key] = {
        name: currentFilesList[key].name,
        lastUpdate: currentFilesList[key].lastUpdate,
        editor: currentFilesList[key].editor,
        url: currentFilesList[key].url,
        type: "新規追加"
      }
    }
  }

  //削除されている場合はシート2にあって最新リストにないので、逆にして検索する
  for(key in filesList) {
    if(!(key in currentFilesList)) {
      updateList[key] = {
        name: filesList[key].name,
        lastUpdate: new Date(),
        editor: "なし",
        url: "なし",
        type: "削除"
      }
    }
  }

  var updateSheet = spreadsheet.getSheetByName(UPDATE_SHEET_NAME);
  //取得した差分をシート1に入力
  for (key in updateList) {
    var rowno = updateSheet.getLastRow() + 1;
    updateSheet.getRange(rowno, 1).setValue(key);
    updateSheet.getRange(rowno, 2).setValue(updateList[key].name);
    updateSheet.getRange(rowno, 3).setValue(updateList[key].lastUpdate);
    updateSheet.getRange(rowno, 4).setValue(updateList[key].editor);
    updateSheet.getRange(rowno, 5).setValue(updateList[key].url);
    updateSheet.getRange(rowno, 6).setValue(updateList[key].type);
  }

  //もし今回差分があった場合、シート2を更新する
  if(Object.keys(updateList).length) {
    listSheet.deleteRows(2, listSheet.getMaxRows()-1);
    for (key in currentFilesList) {
      var rowno = listSheet.getLastRow() + 1;
      listSheet.getRange(rowno, 1).setValue(key);
      listSheet.getRange(rowno, 2).setValue(currentFilesList[key].name);
      listSheet.getRange(rowno, 3).setValue(currentFilesList[key].lastUpdate);
      listSheet.getRange(rowno, 4).setValue(currentFilesList[key].editor);
      listSheet.getRange(rowno, 5).setValue(currentFilesList[key].url);
    }
  }
}

 以下の部分は、自分の環境に合わせて書き換えてください。

//対象とするGoogleDriveフォルダのID ブラウザでアクセスしてURL見れば分かる
var FOLDER_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
//更新日時を記録するスプレッドシートのID ブラウザでアクセスしてURL見れば分かる
var SHEET_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
//編集履歴が入力されるスプレッドシートのシート名(下に表示されるタブのやつ)
var UPDATE_SHEET_NAME = "シート1";
//直前のフォルダ内ファイルのリストが入っているシート名
var LIST_SHEET_NAME = "シート2";

 フォルダ・スプレッドシートどちらのIDもURLになっています。

f:id:tetragon64:20210722164247p:plain f:id:tetragon64:20210722164255p:plain

 赤枠で囲った部分をコピペしてください。

 シート名は、特にこだわりがなければデフォルトのままでいいでしょう。

 また、ファイルの最終更新者を取得するところでAPIを使用しています。左のサービスの「+」からDrive APIを探して追加しましょう。

f:id:tetragon64:20210722164316p:plain

実行してみる

 ここまでできたら、いったん createList を実行してみましょう。この時権限を聞かれるかもしれないので、許可していきます。実行後、何かフォルダに新規追加するなどして今度は updateCheck を実行してみましょう。実行終了後、シート1のほうに新規追加したファイルが表示されていれば成功です。

f:id:tetragon64:20210722164338p:plain

自動実行化する

 自動で実行されないと通知の意味がない! ということで、トリガー機能を使って自動で実行してもらうようにします。便利ですね。

 左のアラーム時計マークをクリックするとトリガー設定画面が表示されます。右下の「+ トリガーを追加」をクリックします。

f:id:tetragon64:20210722164401p:plain f:id:tetragon64:20210722164412p:plain

 実行する関数は「updateCheck」、イベントのソースは「時間主導型」、時間ベースのトリガーのタイプは「分ベースのタイマー」、間隔は「1分おき」に設定することで1分毎に実行されるようになります。

f:id:tetragon64:20210722164425p:plain

 ただ、そもそものファイル数が多かったりフォルダが入れ子構造でたくさん入っていたりすると、1分で終わらず正常に実行されないかもしれません。(そもそも1分おきが「実行開始してから1分」なのか「実行終了してから1分」なのかもわかってない)

 その場合は、プログラムを手直ししてみてください。(最終更新者情報が必要なければAPIごと消すとか、updateCheckで差分があった時のシート2更新処理を差分のみの処理に変えるとか)

 そもそも、GASには実行時間6分の壁があるらしいので、本当に通知がほしいフォルダに絞るのが賢明だと思います。体感的には約70ファイルで長いと1分かかるという感じ。

Slackに通知が来る設定にする

 ここまでできたら、IFTTTを使って他サービスで通知が来るように設定します。

ifttt.com

 今回はSlackに通知を設定します。「Create」。

f:id:tetragon64:20210722164455p:plain

 「If This」をクリックした後、"Google"で検索して「Google Sheets」を選択します。

f:id:tetragon64:20210722164508p:plain f:id:tetragon64:20210722164520p:plain

 「New row added to spreadsheet」を選択します。

f:id:tetragon64:20210722164532p:plain

 「Or copy and paste the spreadsheet URL」欄に今回作ったスプレッドシートのURLをコピペして、「Create Trigger」。

f:id:tetragon64:20210722164548p:plain

 今度は「Then」を設定します。通知内容は好みで設定してください。カラムの内容を設定できるのはありがたいですね。

f:id:tetragon64:20210722164604p:plain

 これで、フォルダ内のファイルに変更があればSlackに通知が来るようになりました。完成!

f:id:tetragon64:20210722170450p:plain

感想

 リモートでの共同作業が増えてくると、バージョンなどのすれ違いがどうしても起きてきがちなので、チャットツールに通知が来るのはすごく楽ですね。  IFTTTだとSlack以外にも色々なサービスと連携できるので、アレンジの幅は広いですね。

 個人的には初めてGASを扱ったのですが、いい頭の運動になりました。情報収集して参考にさせてもらいつつ勢いだけで書き上げたのですが、勉強すればもっと色々なことができそうですね。Googleの手広さがすごい。

 あと、何気にMarkdownで記事を書いたの初めてでした。初めて尽くしで頭がパンクしそう。

参考にさせて頂いたサイト

boomin.yokohama

qiita.com

 今回作ったコードの大元として参考にさせて頂いたサイト。メールで通知が欲しい場合はこちらを参考にするといいと思います。

rikoubou.hatenablog.com

 最終更新者を取得する方法として参考にさせて頂きました。

あとがき

 初めてこういうプログラマ的な記事を書いた気がします。いつも音楽とか旅行とか書いてるのにね。まぁ、たまにはいいじゃないですか。