GASでGoogleドライブの更新をSlack通知・Trelloで管理

こんにちは。

以下の記事で紹介した,Googleドライブの更新をSlackに通知するbotのおはなしです。
pizzacat83.hatenablog.com

ソースコードはこちら。
github.com

概要

更新通知

特定のフォルダの子孫に更新があると,Slackにこんな感じの通知が来ます。(隠していますが,タイトル部分にはファイルのフルパスが表示されています。)
f:id:pizzacat83:20190302230608p:plain:w400

ファイル1つをattachment1つで表すため,100個以上のファイルが一気に更新されるとSlackの仕様で弾かれます。そもそも100行以上のメッセージが投稿されるとスクロールが大変です。Slackは「attachmentは20件までにしようね!」とドキュメントに書いているので,20件を超える場合はファイルとして投稿することによって折りたたみ表示されるようにします。プレーンテキストなのでSlackフリープランのストレージ制限はあまり気になりません。
f:id:pizzacat83:20190306204625p:plain:w400

Trello管理

生きていると時々ファイルが削除されることがあります。Googleドライブの共有設定で削除を禁止することはできないので,クラス共有フォルダ内で削除されるべきでないファイルが削除された場合,復元をする必要があります。消すやついないだろと思うかもしれませんが過去5回くらい消えました。ローカルのを消そうとして,とか。

復元するだけなら管理人(私)が雑に再アップロードすれば良いのですが,管理人はバカなので削除の通知が来ても「/* 後で対応する */」と言って結局忘却します。5回消えて3回忘却しました。ヒューマンエラーは環境の改善で解決されるべきです。

そこで,ファイル削除(復元ToDo)をTrelloで管理することにしました。SlackをTrelloと連携させた上で,ファイルが削除された際,ToDoリストにカードを追加します。 f:id:pizzacat83:20190306212637p:plain:w400

削除された日時の3日後が期限として設定されており,チェックリストには削除されたファイル一覧が表示されます(ただし数が多い場合はチェックリストではなく削除アイテム一覧のテキストファイルへのリンクが添付されます)。
f:id:pizzacat83:20190306210128p:plain

botがSlackにカードへのリンクを投稿し,Trello botが展開してくれます。これによってSlackからの操作が容易になります。
f:id:pizzacat83:20190306210422p:plain

それでも管理人は忘れそうなので,期限を過ぎてToDoに残っているカードはbotが自動で復元します。また,期限は過ぎていないが面倒なので自動修復してほしい場合は,Autofixというボードに移動しておくと自動復元してくれます。

自動復元しなくて良い場合はDoneリストにカードを移動すればよく,「今週中に修正版をアップロードするため消した」のような場合はカードの期限を週末に変更するだけで済みます。以上の操作は全てSlack上から/trelloコマンドで実行できます。

ボードの全体図はこんな感じです。

f:id:pizzacat83:20190330010335p:plain

うちのクラスの管理人は私だけで,Trelloのアカウントを持っている人も少ないので今のところこのボードは私しか使っていませんが,管理人が複数いる場合は担当者にassignすることもできますし,削除してしまった人にメンションしてカードのコメント欄で対応を協議することもできるでしょう。

実装

フォルダ下の更新情報を得る

Drive Activity API

かつては全てのファイルの最終更新日時を再帰的に調べていたんですが,ファイル総数が2000近くあって実行制限の6分以内に終わらなくなりました。Drive Activity APIを用いれば,そんなことしなくても一瞬で更新履歴を取得できます。しかも更新された時刻だけでなく,追加・移動・編集・共有設定変更など更新の詳細も取得できます。エウレカ

ある時刻からの全ての更新履歴を得るコードはこんな感じです。

ルートフォルダのIDを指定する際,クエリのancestorNameに指定する値は類とフォルダのIDにitems/を前置したものであることに注意してください。更新履歴が多い場合はnextPageTokenという値がもらえるので,それを用いてもう一度リクエストすると続きを見ることができます。また,consolidationStrategy{legacy: {}}にすると,同じユーザによる同時期の複数ファイルの追加などを1つにグルーピングしてくれます。

これで得た更新履歴を雑に整形してSlackに投げます。グルーピングされたまとまりの中には個々のActionが入っていて,それをattachmentにする衝動に駆られますが,例えば「ファイル1つを追加」という大きなまとまりの中にはファイル追加・移動・共有権限設定などのActionが含まれていて,それらをいちいち別個のattachmentにするのはあまり好ましくありません。そこで個々のActionは無視して,マクロ的な変更の内容,変更されたファイル一覧,変更したユーザ一覧,変更日時を見ることにします。

変更されたファイルについて,APIはファイルのIDと名前を教えてくれますが,フルパスはわかりません。ファイル名だけでは大抵何のファイルかわからないので,フルパスを取得します。

フルパス取得

そもそもGoogleドライブのファイルシステムは木ではありません。フォルダは複数の子を持つことができ,複数の親をもつことができます。よって,フルパスという概念はありません。

そこで,ファイルの親を再帰的に辿っていき,クラス共有フォルダにたどり着いたルートをフルパスとみなすことにします。ルートは複数あるかもしれませんがそれは気にしません。最短ルートであるかどうかもどうでもいいので雑にDFSします。あるフォルダからルートフォルダへの経路が既知である場合はそれを流用できるので,pathsというMapに記憶させます。

ちなみにMapはGASに実装されていないので,babelしましょう。

ユーザ名取得

Drive Activity APIは変更をしたユーザの情報も教えてくれるのですが,people/1234567890みたいなIDしか教えてくれないので誰のことだかさっぱりです。そこでPeople APIを使います。このgetメソッドを呼ぶと,ユーザの表示名を取得することができます(undefinedの場合もあります)。

undefined結構多いので,対応表を自分で用意したほうが早いかもしれませんね。うちのクラスメイトは全員クラスGoogleグループに登録されていて,それを利用して名前やメールアドレス(→Slackアカウントと紐付け)とか取得できないかなあと思ったんですが,現在@groups.google.comのメンバーを取得できるAPIはないそうです。悲しい。

フォルダを無視する

クラス共有フォルダには1時間ごとに更新されるログとかも入っていて,その更新通知が来ても困ります。無視したいフォルダを登録しておき,あるファイルの親フォルダが全て無視対象である場合に無視する,というルールで更新情報を無視します。

Trello連携

全般

Trello APIはドキュメント通りにリクエストを投げるだけですが,適当にラップします。

カード作成

Card作成→(Checklist追加→Checklistの中身を追加)or URLを添付→SlackにCardのurlを投稿
という流れになります。一つ一つリクエストを投げれば良いです。

自動復元の際にカードと削除されたファイルのID一覧との対応関係がわかるように,スクリプトのプロパティに登録します。

自動復元

カード作成時に記憶しておいたファイルのID一覧を取得して,各IDについて復元を実行します。具体的には,ファイルのコピーを作成し,元のファイルの親フォルダ全てにコピーを追加します。

フォルダの復元はGoogleドライブのファイルシステムがややこしくて,うまくやる方法がまだ思いついていないので,未実装です。助けてください

あとがき

実行時間が6分を超える旧仕様と比べた高速化を目指して大幅改修をしたわけですが,改修後も変更が多いと実行に30秒とかかかっていて,もう少し高速化したいです。高速化できそうな箇所はいくつかあるので,ぼちぼちやっていきます。