見出し画像

GASを使ってフォルダ内の変更をSlackに通知する①

やりたいこと

まず,この記事で紹介するもののイメージは以下の図です.読者のやりたいことが違ったらすぐブラウザバックしてもっと良い記事を探しに行きましょう

GASのイメージ図

長くなるので,2つの記事に分割して,今回はスタンドアローン側のプログラムメインで紹介します.
次の記事では,実際の実行スクリプト側を紹介します.

参考になった記事

今回の記事を書く上で,参考にした記事になります.大変お世話になりました.今後ともよろしくお願いします.

ファイル構成

今回,特に長いプログラムというわけではないのですが,
Slack通知のmain関数たちと,トリガーのクラスは随分と毛色が違うプログラムだと思ったので,別のファイルにしてみました.

システムのイメージ図に書いているのですが,スタンドアローンのプロジェクトを,ドライブの最も上の階層に準備しておきます.

ファイル構成

DrivetoSlack.jsを書く

今回は,いくつかのフォルダに対して同じような処理をしたくなるかもしれないと思い,スタンドアローンで基本処理を書きました.
そうすれば実行スクリプトで指定する内容が少なくなります.やったね.

先の参考記事のプログラムを大きく受け継いでいます.ご容赦ください.

通知の時の名前を決める

なともあれ通知の時の名前です.
推しの名前とかにするともっとやる気も上がることでしょう.
今回は「Notification」さんにします.

// Slackへ通知する際の名前
const username = 'Notification';

main関数を書く

実行するためのmain関数を書いていきます.

対象のフォルダのURLと,Slackの通知先チャンネルは実行スクリプト側から指定するので,main関数の引数に設定します.
フォルダ内にあるものが変更されれば通知され,なければ通知しないで条件分岐を設定します.
通知するときは,変更時刻込みで表示します.表示する中身は後述します.

// Mainメソッド
// 指定フォルダ(下位フォルダ含む)に更新されたファイルがある場合にSlackへ通知
function main(folderUrl, webhookUrl) {
  const folder = DriveApp.getFolderById(folderUrl);
  // const oneHourAgo = getOneHourAgo();
  const oneDateAgo = getOneDateAgo();
  const allUpdatedFiles = getAllUpdatedFiles(folder, oneDateAgo);

  //Uptadateなし→通知なし
  if (allUpdatedFiles == '') {
    return;
  }

  //Updateあり→Slack通知(日時,ファイル名)
  const fmtOneDateAgo = Utilities.formatDate(oneDateAgo, 'JST', 'YYYY-MM-dd HH:mm:ss');
  let data = `${fmtOneDateAgo}以降,修正されたファイルリスト`;
  allUpdatedFiles.forEach((updatedFile) => {
    data = `${data}\n●${updatedFile}`;
  });
  sendToSlack(data, webhookUrl);
}

更新日時を取得する

通知内容に変更日時とかも加えるので,ここで取得します.
1時間ごととか,編集中にも通知が来たらいくら推しであっても鬱陶しいので,一日おきに挨拶をしてくれれば良いです.

以降,カッコつける練習にアロー関数を使っていますが,通常のfunctionで定義しても問題ないです.

// 1日前の時間を取得
const getOneDateAgo = () => {
  const oneDateAgo = new Date();
  oneDateAgo.setDate(oneDateAgo.getDate() - 1);
  return oneDateAgo;
}

ファイル変更をチェックする

ファイル,もしくは下位フォルダに変更があったかをチェックします.
チェックにはファイルの最終更新時間を使います

  1. ファイルの最終更新時間とプログラム実行の1日前の時間とを比較する

  2. 1日前の時間よりも更新時間が進んでいたら更新情報をリストに追加する

  3. 下位フォルダが存在したら,その中身についても同様にチェックする

  4. 更新情報がのったリストをmain関数に返してあげる

// 指定フォルダ(下位フォルダ含む)に1日に更新されたファイルリストを抽出
const getAllUpdatedFiles = (folder, oneDateAgo) => {
  let updatedFiles = []; //ファイルリスト
  const files = folder.getFiles(); //フォルダ内のファイルをすべて取得

  //ファイルをすべて見て,最後にUpdateされた日に変更があればファイルリストupdateFilesに追加する
  while (files.hasNext()) {
    const file = files.next();

    //更新時間が1時間前の時刻と比較→進んでいたらリストに追加する
    if (file.getLastUpdated() >= oneDateAgo.getTime()) {
      //表示:アップデート時刻,ファイル名,フォルダ名,URL
      const fileUpdated = Utilities.formatDate(file.getLastUpdated(), 'JST', 'YYYY-MM-dd HH:mm:ss');
      const fileInfo = `Updated=${fileUpdated}, FolderName=${folder.getName()}, FileName=${file.getName()}`;

      //リスト追加
      updatedFiles.push(fileInfo);
    }
  }

  //下位フォルダが存在した場合,フォルダも同様にすべて見て,更新を確認する
  const childFolders = folder.getFolders();
  while (childFolders.hasNext()) {
    const fileInfo = getAllUpdatedFiles(childFolders.next(), oneDateAgo);
    updatedFiles = updatedFiles.concat(fileInfo);
  }
  return updatedFiles;
}

Slackへ通知を送る

ここで,Slackに通知を送る関数を書いていきます.
実行スクリプトとからもらったwebhookのURLと,変更メッセージを引数にして,通知設定をします.
webhookの設定については以下の記事を参考にしましたので,ご参考までに.

// Slackへ通知
const sendToSlack = (msg, hook) => {
  //プロパティスクリプトよりWebhookを取得する
  //値にはSlackのチャンネル番号を使用する

  // 通知する内容
  const param = {
    'username': username,
    'text': msg
  };

  // paramをJSON文字列に変換
  const payload = JSON.stringify(param);

  // APIを叩くにあたってどのような仕様で通信を行うか、どのような情報を送るかを指定
  const options = {
    'method': 'post',
    'headers': {
      'Content-Type': 'application/json'
    },
    'payload': payload
  };

  // APIの呼び出し(ここで実際に通知される)
  UrlFetchApp.fetch(hook, options);
}

以上で,通知を送るための設定は終わりました.DrivetoSlack.gsは完成です!!
以降は,Trigger.gsになります.

Trigger.gsでトリガーと設定する

毎日送ってもらうためのトリガーを設定します.
定刻に送ってもらうには,2つのトリガーを設定しなくてはいけないらしいです.(参考にした記事2つ目を見てください)
本当はこんなことをしなくても良いのかもしれませんが,どうせなら同じ時間に動いてほしいので,仕方ありません.1つ目のトリガーはスクリプトで設定し,2つ目はGUIで設定していきます.

  1. ファイルチェックと通知を実行するかのトリガー

  2. 1つ目のトリガーがぴったり定刻に行われるために,前もってGASを起動するトリガー

また,トリガーは溜まってしまうらしく,このままでは20個の制限に引っかかってしまうので,使い終わったトリガーは削除してもらうようにします.

そして,以下の記事によると,GASでライブラリからClassを呼び出すには,グローバル領域にセットする必要があるらしいので,それも追加します.以下の記事を参考にしました.

//定刻でmain関数を実行するためのトリガークラスの作成
//これでSlack通知が欲しい共有フォルダ先のトリガー関係の関数がすっきり記述できます
class Trigger {
  //コンストラクタの設定 functionName←トリガーの対象となる関数(slackNotify)
  constructor(functionName) {
    this.functionName = functionName;
  }

  //指定日時のトリガーを設定.時間はフォルダ先で設定する.
  createTimeBased(triggerTime) {
    ScriptApp.newTrigger(this.functionName).
      timeBased().
      at(triggerTime).
      create();
    return this;
  }

  //トリガーの削除
  //トリガーは1アカウントにつき最大20件らしいので,使ったら消すようにします
  delete() {
    const triggers = ScriptApp.getProjectTriggers();
    triggers.forEach(trigger => {
      if (trigger.getHandlerFunction() === this.functionName) {
        ScriptApp.deleteTrigger(trigger);
      }
    });
    return this;
  }
}

// クラスをglobal領域にセットする
this.trigger = Trigger

おわり

お疲れ様でした!!!!
ここまで読んでいただき,大変ありがとうございました.少しでも参考になるところがあればいいなと思います.

次の記事では,実行側のスクリプトを紹介して,どういったセッティングをすればいいのかを紹介します.ぜひそちらもご覧いただければ幸いです.


この記事が気に入ったらサポートをしてみませんか?