Google Driveの更新通知を受け取って記録を取る【GAS】

GoogleはBoxと違って簡単にGoogle Driveの変更通知を受け取って処理といったことが出来ません。他にもパスワード保護も無いし、外部共有の制限などの機能が大雑把など細かい点で機能不足が目立ちます。

しかし、Drive APIを利用する事でフォルダやファイルの監視をする事が可能という情報を耳にしたので、今回Google Apps ScriptとGoogle Drive API、通知を受け取る窓口としてGoogle Cloud Functionsを使って構築してみようと思います。

Boxでの変更通知のテクニックは以下のエントリーをご覧ください。

Boxファイル監視をPower AutomateでExcelに書込み - Webhook v2編

今回利用するサービスとスプレッドシート

今回のプログラムはGoogle Apps Script単体では構築が出来ません。そこで、次項の制限事項にある内容をクリアする為に、Google Cloud Functionsを利用しています。関連エントリーは以下のページです。

Google Apps ScriptでCloud Functionsに実行制限付でアクセス 最新版【GAS】

仕組みと制限

Drive APIを使って特定のファイルやドライブ全体に対して、監視するチャンネルを追加し、変更を受け取ってシートに内容を書き込んだり、通知をChatで送ったりが今回の目的。しかし、Google Apps Scriptの仕様上の問題で、監視チャンネルの設置は出来るものの、変更通知を受け取る事が出来ず目的を達成出来ない。

という事で以下の制限を踏まえた上で、仕組みの内容を構築準備する必要性があります。

GASの制限

Drive APIにチャンネルを設置して、有効期限が来る前に再設置する。また、GCFからの書き込み依頼を受けてシートに書き込むのが今回の役割。本来は通知も受け取りそのままGASで完結したい所が、Google Apps Scriptには以下の仕様がある為、それが叶わず。

  • Drive APIからの通知にはリクエストヘッダにカスタムヘッダ項目が含まれていて、ここにどんな変更内容なのかが含まれているが、GASはリクエストヘッダの中身を取得する事が出来ない。(X-Goog-Resource-Stateの値が必要になる)
  • 公開されていない制限があり、watchのAPIのQuotaは変更監視出来る上限が決まってる(StackOverFlowによると60秒未満、70ファイルを超えるwatchを設定すると403エラーになる)。またこの上限は割増申請が出来ない
  • 監視チャンネルの寿命は、最大24時間。そのリミットが来る前にチャンネルを削除して再設置する必要がある。
  • Drive APIから飛んでくる「resourceIdとChannel ID」が削除時に必要になるのでスクリプトプロパティに格納しておく必要がある
  • watchをする前にStartPage Tokenというものが必要。
  • レスポンスを受けるサイトの所有権証明をsearch consoleにてしなければ行けないので、GASだけだと難しい。

仕組み

役割分担的には以下のようなイメージ

  • GAS:Drive APIに監視チャンネル作ったり、24H毎に設置し直したり。またGCFからの書き込み命令を受け取ってシートに書き込む役割
  • Drive API:ドライブやファイルに対する監視とGCFへの通知を行う
  • GCF:Drive API側からの通知を受け取り解析、GAS側へ書き込み命令を送る

この仕組を応用する事で、Google Driveの変更通知だけじゃなくAdmin SDKのDirectory APIからの通知を受け取って処理などが出来るようになるので、色々と使えそうです。

事前準備

今回の仕組みはGCP側の機能を利用する為、いくつかの事前準備が必要になります。予め、GCP側にはDrive APIを有効化しておいたプロジェクトを用意しておく必要があります。また、サービスアカウントを使う必要があるので、それらの設定も必要になります。詳細は以下のエントリーを参考にしてみてください。

Google Cloud Consoleを弄ってみる

GAS側での準備

APIの有効化

今回はGoogle Drive APIを利用するので、スクリプトエディタに入って、以下の作業が必要になります。

  1. スクリプトエディタを開く
  2. エディタの左側、サービスの横の+をクリックする
  3. Drive APIを探して選択し、追加をクリック

これで、Drive APIの各種メソッドが利用する事ができるようになります。

図:Drive APIを追加する

プロジェクトを移動

まずは、GCP側を開いて対象のプロジェクトを開く。Google Apps Scriptとプロジェクトを紐付けにする必要があります。連結する手順は以下の通り

  1. Google Cloud Consoleを開く
  2. 左上にある▼をクリックする
  3. ダイアログが出てくるので、新規プロジェクトを作るか?既存のプロジェクトを選択する。この時、Google Workspaceであれば選択元は「自分のドメイン」を選択する必要があります。
  4. プロジェクト情報パネルから「プロジェクト番号」をコピーする
  5. 対象のGoogle Apps Scriptのスクリプトエディタを開く
  6. サイドバーからプロジェクト設定を開く
  7. プロジェクトを変更ボタンをクリック
  8. GCPのプロジェクト番号に、4.の番号を入れてプロジェクトを設定をクリック

今回のこの変更だと1つ作ったプロジェクトに集約する必要があるので、クォータについてプロジェクト毎のカウントだったので問題なかったものが、集約されることで、クォータに引っ掛かる可能性があります。

図:プロジェクト番号をコピーしておきます

図:プロジェクト変更画面

デプロイを行う

GAS側で処理するためのコードを記述したら、doPostの受け口を有効化するためにデプロイをします。

  1. スクリプトエディタを開く
  2. 右上のデプロイをクリック
  3. 新しいデプロイをクリック
  4. 種類の選択ではウェブアプリを選択し、次のユーザとして実行は自分にしておきます。
  5. アクセスできるユーザは、ドメイン名内のユーザとして起きます
  6. 末尾がexecで終わるURLが発行される。これがGCF側のNode.jsで送信先として指定するURLとなるので控えておく。
  7. 次回以降コードを編集して再デプロイ時はデプロイを管理から同じURLにて、新しいバージョンを指定して発行することが出来ます。

APIを有効にする

Google Cloud Console側でもAPIを有効化する必要性があります。

  1. GCPのプロジェクトを開く
  2. 左サイドバーからAPIとサービスにて、「APIとサービスの有効化」をクリックする
  3. driveと検索すると出てくるので、クリックします。
  4. 有効化をクリックします。
  5. 認証情報の作成は不要です

図:有効化をしておくだけでOK

Cloud Functionの準備

インスタンスの準備

今回はCloud Functionsは受け専門でスプレッドシート側へ解析内容を転送するのが仕事なので、以下のような手順でIncoming Webhookのようなスタイルで構築します。

  1. 右上のハンバーガーメニュー(≡)をクリックし、サーバーレス項目にあるCloud Functionsをクリック
  2. 関数を作成をクリック
  3. 環境は第一世代で問題なし
  4. 関数名はデフォルトのfunction-1のままで問題なし
  5. us-central1のリージョン、トリガーはHTTP未認証の呼び出しを許可にチェックをする
  6. 保存をクリックする
  7. ランタイム、ビルド、接続、セキュリティの設定を開く
  8. 続けて下にある「ランタイム、ビルド、接続の設定」をクリック
  9. メモリは256MBを指定、使用するサービスアカウントの指定をして次へをクリック(サービスアカウントを作っていない場合には、App Engine Default service accountここでデフォルトのものが作成されます)
  10. 次へをクリックする
  11. ランタイムでは、今回はNode.js 18を指定エントリポイントは最初に実行する関数名を指定します。今回はwebhookFunctionとしました。
  12. これでとりあえず、準備は完了。とりあえずデプロイボタンを押します。但しこのデプロイは緑色のチェックマークがついたら成功なのですが、かなり時間が掛かります。
  13. この時、トリガーURLのリンクをコピーしておきましょう。

 

図:Clound Functionsの設定画面

図:インスタンス設定の画面

図:ランタイムの指定

requestモジュールを追加

GAS側へGCF側から通知を送るためにGAS側のdoPostで用意したエンドポイントに更新されたよという通知を送るために利用します。GCF側のNode.jsにあるpackage.jsonにモジュールを追記しておきます。これでrequireでrequestモジュールが使えるようになります。

図:requestモジュールを追加しておく

Search Consoleで所有権証明

Drive APIからの通知を受けるサイトの所有権証明をしなければなりません。以下の手順で所有権証明を行います。

  1. Search Consoleにログインする
  2. すでに使ってる人は、左上のドメイン一覧のドロップダウンをクリックして、プロパティを追加をクリック
  3. URLプレフィックスに対して、Cloud Functionsで取得しておいたトリガーURLを入れる
  4. 続行をクリック
  5. 所有権の確認では、HTMLタグを選んでコピーする
  6. Cloud Functionsのページに戻って、編集をクリックし、コードの編集画面までゆく
  7. 以下のようにコードを書き換えて、デプロイする。<html><head>ここにHTMLタグを記述</head></html>の形式で返してあげるようにします。

    res.status(200).sendの中身に5.で取得しておいたHTMLタグをそのまま返すように記述を追加する。デプロイしてからしばらく待つのがポイント。すぐ確認をしてしまうと失敗する。
  8. Search Consoleの画面に戻って、「確認」をクリックする
  9. 所有権確認が完了したら、閉じる。
  10. GCP側に戻って、APIとサービスのOAuth同意画面を開く
  11. アプリを編集をクリック
  12. 認証済みドメインにトリガーURLのhttpsと関数名を除いたドメイン名を追加登録しておく(例:us-central1-gasgas-99999.cloudfunctions.net)

図:URL PrefixにトリガーURLを入れる

図:所有権確認が完了しました。

図:GCP側でも認証済みドメインとして追加しておく

ソースコード

GCF側コード

  • gaspointにはGAS側でデプロイしたウェブアプリケーションのURLを入力します。
  • Drive APIで受け取ったカスタムヘッダの中身を見てGAS側へ通知を送るだけのシンプル仕様
  • HTTPリクエストには古いモジュールだけれど、requestモジュールを今回は利用しています。現在はnode-fetchなどを使うと良いでしょう。
  • bodyの中身はGAS側ではe.postData.contentで受け取ることが可能です。

GAS側コード

今回は、ドライブ全体の更新履歴を取るパターンです。ファイル単位の場合はgetStartPageTokenが不要で、またDrive APIへのリクエストエンドポイントが異なるので、GASの場合は「Drive.Files.watch」を使うことになります。同じく引数にオプションとファイルIDを指定することになります。

  • webhookurlにはGCF側のトリガーURLを入力します。
  • startSettingは初回の1回だけ実行してシートのIDを格納しておきます。
  • resetWatchにてDrive APIに対して監視のチャンネルを作成します。expirationは有効時間で最大24時間ですが、ミリ秒で指定します。
  • idはユニークなIDを指定するので、uuidを生成し割当
  • pageTokenを取得してから投げる理由は、このページトークンによって、次回の通知時には前回取得したもの以降の更新リストを取得できるようにするためです(でないと何度も同じ更新通知がかぶって入ってきてしまう)
  • CHが作りっぱなしにならないように、stopWatchで一度削除してから新しいchannelIdとresourceIdを保存し直しています。
  • doPostはGCF側からの通知に従って、pageTokenを使って更新されたものだけのリストをスプレッドシートに記述しています。
  • 複数リクエストが来る場合を想定してLock Serviceを使って排他制御を入れています。
  • シート書き込み終わったら最後に新しいpageTokenを取得してプロパティに格納し直します。
  • 今回は用意していませんが、resetWatchを24時間ごとに実行し直すようなスクリプトトリガーを仕掛けておくと、永続的にファイルの更新をウォッチすることが可能になります。

ドライブ全体の場合膨大な内容が来るので、parentなどのディレクトリのIDなどでフィルタして書き込みするであったり、またドライブ全体ではなくファイル単位で指定で特定のファイルだけウォッチすると良いでしょう(ただし、70個程度しかCH設定が出来ないので要注意)

図:無事に更新履歴を取ることが出来るようになった

排他制御でGoogle Apps Scriptを安全に実行【GAS】

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)