Google Driveの変更通知をGoogle Cloud Functionsで受け取る

# この記事でやりたいこと

Google Driveで変更(ex. ファイルの追加や削除など)が発生したら、Google Cloud Functionsに通知する方法を説明する記事である。

Google Apps Scriptなどで実現する場合、 *1分間隔でポーリングするの方法* でも同様のことは行えるかもしれないが、[Drive API - Push Notification](https://developers.google.com/drive/api/v3/push)を使用すれば、 **Google Driveのディレクトリ内の変更発生時に以下の方法で指定したURLをリクエストをしてくれる** APIが存在する。今回は、それを使う。ポーリングしなくてもいい。最高。

# 使用するもの

- Google Cloud Functions
- Python

---

# Step1. Google Cloud Functionsでfunctionを作成する

<img width="704" alt="関数の作成_–_Cloud_Functions_–_DealConnect_–_Google_Cloud_Platform.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/23cce885-6c10-bf8a-25a1-ffd3fec26a19.png">

トリガーを *HTTP* とする、functionを作成する。今回の例ではNode.jsを使用するが、後述するHTTP tagをレスポンスできればどの言語でも問題ないハズ。

とりあえず、そのまま作成する

# Step2. URLを所有していることを登録する

Drive APIでPush Notificationしてもらうためには、URLを所有していることを証明しなければならない。

Google Search Consoleから行う。

https://search.google.com/search-console/about

<img width="836" alt="Google_Search_Console_へようこそ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/22f5907b-2d13-c6fb-9ff9-4d44e9945d60.png">

確認方法として、`HTML タグ` を選択する。HTMLタグはコピーしておく。

<img width="611" alt="Google_Search_Console_へようこそ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/2cf7592a-6c5b-efbf-c6c2-bcd41f1ca82c.png">

# Step3. functionでHTMLタグをレスポンスするようにする

functionに戻って、先程コピーしたHTMLタグをレスポンスするように **ソースコードを書き換える**。その際、

```html
<html><head>先程のHTMLタグ</head></html>
```

とすることを忘れないようにする。

<img width="576" alt="関数の編集_–_Cloud_Functions_–_DealConnect_–_Google_Cloud_Platform.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/254769b4-ad70-097a-11ee-14b34ad1f213.png">

デプロイする。

# Step4. URLを確認してもらう

functionへ行った変更が行われたら、
Search Consoleまで戻ってきて、確認を実施する。

<img width="598" alt="Google_Search_Console_へようこそ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/1fc9196e-7f48-5d81-3f68-61ad97580c3e.png">

問題がなければ、以下のように所有権を証明できた旨の表示が行われる

<img width="687" alt="Google_Search_Console_へようこそ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/c4a53bff-dee4-32d0-e213-ad47154dbe0c.png">


# Step5. GCPプロジェクト上でドメインを確認する

通知を送信するために、ドメインの所有権を証明をGCPプロジェクト上で行う必要がある。以下のドメイン確認のページに向かう。

<img width="559" alt="概要_–_サポート_–_DealConnect_–_Google_Cloud_Platform.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/8a015ca8-354b-9662-97ed-803bce7dcef3.png">


そして、functionのトリガーとなるURLを追加する

# Step6-1. サービスアカウントを作る

Drive APIで通知先となるURLを登録するためには、アクセストークンを発行する必要がある。そのため、Google Driveにアクセスできるサービスアカウントを作成する。

まず、サービスアカウントの作成画面に向かう。

<img width="921" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f333337372f30623839323865652d613266302d393039632d613337322d6439393136316334626131652e706e67.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3377/e4860a90-db4e-a1d6-90cb-1d971d07837a.png">


作成途中で、 **キーを作成** することが出来るので、JSONタイプのキーをダウンロードする。

# Step6-2. アクセストークンを取得する

以下のPythonのコードスニペットを使って、アクセストークンを取得する

```py:access_token.py
from oauth2client.service_account import ServiceAccountCredentials
from oauth2client.client import OAuth2Credentials
credentials = ServiceAccountCredentials.from_json_keyfile_name(
      'key.json', scopes=['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.readOnly'])
res = credentials.get_access_token()
print(res.access_token)
```

`access_token.py` と同じディレクトリ内に、先程落としてきたjsonを `key.json` とリネームして実行すれば良い。なお、実行のためには以下のパッケージなどをインストールしておく必要があるため、適宜対応する。

必要であれば以下で通すPATHは `.bash_profile` 等で通すように設定しておくと良い。

```zsh
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ python get-pip.py
$ export PATH="$HOME/Library/Python/2.7/bin:$PATH"
$ pip install --upgrade oauth2client
$ python access_token.py # 上記のコードスニペット。実行すると、アクセストークンが出力される。
```

## 参考

- https://pip.pypa.io/en/stable/installing/
- https://qiita.com/tom-u/items/134e2b8d4e11feea8e12
- https://github.com/googleapis/oauth2client


# Step 7. ディレクトリ変更時のURL通知を登録する

Drive APIによる通知では、以下の2つの変更の通知を受け取ることが出来る。

- [/files/watch (特定のファイルの更新)](https://developers.google.com/drive/api/v3/reference/files/watch)
- [/changes/watch (ドライブ全体の変更)](https://developers.google.com/drive/api/v3/reference/changes/watch)

今回はドライブ全体の変更を受け取ることにする。

まずは、`pageToken`の位置を取得する。

```py:start_page_token.py
import requests
token = 'XXXXX' # アクセストークン
header = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
}
r = requests.get(url='https://www.googleapis.com/drive/v3/changes/startPageToken', headers=header)
print(r.text)
```

表示される `startPageToken` を控えて、
以下のコードスニペットに、必要な箇所を穴埋めする。

```py:changes_watch.py
import uuid
import requests
import json
channel_id = str(uuid.uuid4())
SCOPES = ['https://www.googleapis.com/auth/drive.file',
         'https://www.googleapis.com/auth/drive.readonly',
         'https://www.googleapis.com/auth/drive']
token = 'XXXXXXXXXXXXXXXXXXXXXX' # 先程のアクセストークンを貼り付ける
header = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
}
body = {
    "id": channel_id,
    "type": "web_hook",
    "address": f'{XXXXXXXXXXXXXXXXXX}' # functionのトリガーとなるURL
}
page_token = '0000' # 先程控えた pageToken
r = requests.post(url=f'https://www.googleapis.com/drive/v3/changes/watch?pageToken={page_token}', data=json.dumps(body), headers=header)
```

以上を実行することで、 **マイドライブ配下で、何かしら変更が発生すると、functionが実行される** 様になる。GCPのログで、functionが起動されていることを確認してみてください。