@kyanny's blog

My thoughts, my life. Views/opinions are my own.

給与明細の PDF ファイルを自分の Google Drive にアップロードしたら妻との LINE グループチャットに通知するシステムを作った。二年半前から欲しかったけど単に IFTTT をかますだけではうまくいかず、毎月一回だしということで手作業でやっていた。怠惰さが足りない。プログラマの風上にも置けない。PDF ファイル自体は、従業員向けに提供されているシステム(外部の会社のもの)からダウンロードする形。

うまくいかなかった理由は、共有フォルダの中に月毎のサブフォルダを作ってそこに PDF をアップロードしてるのだが、IFTTT の Google Drive トリガーはサブフォルダ内の変更を検知しないので Google Apps Script で頑張る必要がある。GAS 全然詳しくないのでいつも書き方を調べるだけでエネルギーと可処分時間を使い果たしてしまっていた。

今回は GitHub Copilot にコードを書かせた。オリジナルのプロンプトとコードはもう残ってないが、「Google Drive のフォルダにファイルを置いたら通知する google apps script を書いて。サブフォルダに追加したファイルも検知するようにして」みたいなプロンプトを英語で書いて、吐かれたコードを gas のエディタに貼り付けて実行・デバッグした。

最終系は以下のようなコードになった。IFTTT の Email アプリで Send IFTTT an email tagged トリガーを作り #payslip というハッシュタグを指定し、gas から #payslip を件名に含めたメールを送信している。とりあえずファイルの URL を含めているけど、サブフォルダ名(YYYY-MM)の方が意味があって親切かもしれない。ファイル名もなんか無機質な数字の羅列だったりするし。

function monitorFolder() {
  // Replace "Folder Name" with the name of your folder
  var folder = DriveApp.getFoldersByName("GitHub").next();
  var fileIds = getAllFileIds(folder);
  var previousFileIds = getPreviousFileIds();
  
  // Check for new files on each run of the script
  var newFileIds = fileIds.filter(function(id) {
    return previousFileIds.indexOf(id) == -1;
  });
  
  // Send a notification email if there are new files
  if (newFileIds.length > 0) {
    var subject = "給与明細をドライブに保存しました #payslip";
    var body = "";
    newFileIds.forEach(function(id) {
      var file = DriveApp.getFileById(id);
      body += file.getName();
      body += "\n";
      body += file.getUrl();
      body += "\n";
      body += "\n";
    });
    MailApp.sendEmail("trigger@applet.ifttt.com", subject, body);
  }
  
  // Update the previous list of file IDs
  setPreviousFileIds(fileIds);
}

function getAllFileIds(folder) {
  var fileIds = [];
  var files = folder.getFiles();
  while (files.hasNext()) {
    fileIds.push(files.next().getId());
  }
  var subfolders = folder.getFolders();
  while (subfolders.hasNext()) {
    fileIds = fileIds.concat(getAllFileIds(subfolders.next()));
  }
  return fileIds;
}

function getPreviousFileIds() {
  var scriptProperties = PropertiesService.getScriptProperties();
  var previousFileIds = scriptProperties.getProperty("previousFileIds");
  if (previousFileIds) {
    return JSON.parse(previousFileIds);
  } else {
    return [];
  }
}

function setPreviousFileIds(fileIds) {
  var scriptProperties = PropertiesService.getScriptProperties();
  scriptProperties.setProperty("previousFileIds", JSON.stringify(fileIds));
}

何度か Copilot と試行錯誤した。

  • 「ファイル ID のリストを定期的に取得して比較します」とかアルゴリズムを説明しておきながら、最初の版では前回の結果をどこにも保存しない実装だったので、これだとダメじゃね?と指摘したらプロジェクトのプロパティに JSON 文字列の形で保存し実行ごとにプロパティから復元したリストと最新のリストを比較する実装を出してきた。合格。
  • 何度かやってるうちに動かなくなり、調べてみると大量のトリガーが追加されてて「これ以上トリガーを追加できない」というエラーで失敗するようになっていた。スクリプトでトリガーを追加する処理が書かれていたせいで、そのコードが何回も実行されていたからだった。その部分を削除して、トリガーは一つ残して残りを削除したら期待通りに動くようになった。

GitHub Copilot の生産性の高さを初めて体験できた気がする。こういうほとんど書き捨てのような、複数人で継続的にメンテナンスするようなものじゃなく、定番のやり方があるコードを書かせるのには確かに便利だ。自分で調べながらだととてもじゃないがやる気が続く間に完成させられなかっただろう。プロパティに JSON で保存というアイデアも思いつかなかったかもしれないし。そして、書かれたコードを読んで評価し、おかしなところを発見して直せるだけのプログラミングの知識・リテラシーがないとうまく使いこなせない、というのも確かにその通りだなと思った。