Google Drive Web APIを使う(node編)


アナログなKanbanの状態を定時に写真を撮って保存して、上海のチームメンバーと共有するため、Raspberry Piにhubotをいれてアップするこのシリーズ。今回はGoogle Drive Web APIを使って撮った写真を保存するやり方をまとめておく。

## 参考リンク

- [Google Drive Web APIのトップページ](https://developers.google.com/drive/v2/reference/)

## 認証について

- Google APIの認証には大きく分けて2つある。OAuthとSimple API Key
- Google Drive内のリソースにアクセスするので、Simple Keyは使えない

### OAuth2認証

- Google APIのOAuth2 にはアプリが対象とする環境によっていくつかやり方がある。
- 今回はhubotに組み込む。常時HTTPサーバーを立ち上げることもできるが、認証情報はひとつでいい。今回は[インストール済みアプリケーション](https://developers.google.com/accounts/docs/OAuth2InstalledApp)を選んだ。[(Youtubu APIの認証方法のページだが、Google Drive Web APIの解説に日本語はないし、同等のことを解説しているので貼っておく。)](https://developers.google.com/youtube/v3/guides/authentication?hl=ja#installed-apps) 
- OAuth2認証を利用するにはまずGoogle Developer Consoleで認証情報を取得する。[こっちもYoutube APIの認証方法のページは日本語で、Google Driveでも同様なので、手順が書かれたリンクを貼っとく。](https://developers.google.com/youtube/registering_an_application)

#### アクセストークンの取得

- APIのアクセスに必要なaccess_tokenを取得するため、まず下記のコードを書く。

```get_tokens.js
var google = require('googleapis'),
    readline = require('readline');

var CLIENT_ID = 'あなたのアプリケーションのCLIENT_ID',
    CLIENT_SECRET = 'あなたのアプリケーションのCLIENT_SECRET',
    REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob',
    SCOPE = [
      'あなたのアプリケーションで必要なスコープを配列形式で',
      'https://www.googleapis.com/auth/drive.file'
    ];

var oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);

var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

function getAccessToken(oauth2Client) {
  // OAuth2認証のためのURLを生成する
  var url = oauth2Client.generateAuthUrl({
    access_type: 'offline', // refresh_tokenが必要なので、offlineを指定
    scope: SCOPE 
  });

  console.log('右記のURLをブラウザで開いてください: ', url);
  rl.question('表示されたコードを貼り付けてください:', function(code) {
    // request access token
    oauth2Client.getToken(code, function(err, tokens) {
      console.log('トークンが発行されました');
      console.log(tokens);
      console.log('上記の情報を大切に保管してください');
    });
  });
}

var fs = require('fs');

getAccessToken(oauth2Client);

```

- get_tokens.jsを実行する。

```shell
$ node get_tokens.js
```

- 指示されたとおり、URLを開くと、OAuth認証のページが開く

![許可のリクエスト.png](https://qiita-image-store.s3.amazonaws.com/0/4244/7e6151f9-8c50-49dd-9174-50cc61dc6b98.png "許可のリクエスト.png")

- 承認するとこんな感じでアクセストークンを取得するためのトークンが発行される。

![Success_code.png](https://qiita-image-store.s3.amazonaws.com/0/4244/d5c98ef9-6f04-0ae0-749a-dc7bd0432c28.png "Success_code.png")


- このトークンを先ほどのスクリプトの「表示されたコードを貼り付けてください:」の右に貼り付ける
- するとaccess_tokenとrefresh_tokenが発行される。access_tokenの有効期間は1時間。
  - 今回は多くても1時間に1回アクセスしないので、必ずrefresh_tokenを使ってaccess_tokenを取得するようにした。

### スコープの話

Google Drive Web APIにあるスコープは下記の通り

| Scope  | 意味     |
|--------|---------|
| https://www.googleapis.com/auth/drive.file | アプリケーションからファイルを作成する、もしくは開くことができる(Per-file access to files created or opened by the app) |
| https://www.googleapis.com/auth/drive |  完全にユーザーのファイルにアクセスすることがゆるされるスコープ。どうしても必要な時にだけこのスコープを要求する。https://docs.google.com/feedsのスコープに許可されたトークンは、https://www.googleapis.com/auth/driveのスコープのトークンとと同じように扱う。(Full, permissive scope to access all of a user's files. Request this scope only when it is strictly necessary. Tokens with scope https://docs.google.com/feeds are accepted and treated the same as tokens with scope https://www.googleapis.com/auth/drive.)|
| https://www.googleapis.com/auth/drive.apps.readonly | ユーザーがインストールしたアプリケーションの一覧に対し読み込み専用でアクセスを許可する (Allows apps read-only access to the list of Drive apps a user has installed.)|
| https://www.googleapis.com/auth/drive.readonly | ファイル自体とそのメタ情報に読み込み専用でアクセスを許可する (Allows read-only access to file metadata and file content) |
| https://www.googleapis.com/auth/drive.readonly.metadata | ファイルのメタ情報の読み込みは許可するが、ファイルのダウンロードや読み込みは許可しない (Allows read-only access to file metadata, but does not allow any access to read or download file content) |
| https://www.googleapis.com/auth/drive.install | ユーザーにアプリケーションのインストールを進める特別なスコープ (Special scope used to let users approve installation of an app) |	 
| https://www.googleapis.com/auth/drive.appfolder | アプリケーションのデータフォルダにアクセスすることを許可するスコープ (Allows access to the Application Data folder) |
| https://www.googleapis.com/auth/drive.scripts | Google Appsのスクリプトファイルへのアクセスを許可(Allows access to Apps Script files) |	

## APIのメモ

- [Google API Explorer](https://developers.google.com/apis-explorer/#p/)でAPIのお試しができる
- [File#insert](https://developers.google.com/apis-explorer/#search/drive.files.insert/m/drive/v2/drive.files.insert) [(Doc)](https://developers.google.com/drive/v2/reference/files/insert) でアップロード
  - 特定のパスにアップロードしたい場合は下記のようにparentsを指定する
- [File#list](https://developers.google.com/apis-explorer/#search/drive.files.list/m/drive/v2/drive.files.list) [(Doc)](https://developers.google.com/drive/v2/reference/files/list) でDriveのリソースを一覧で取得
  - qというパラメータが検索条件を指定するクエリ。[パラメータについてはこちら](https://developers.google.com/drive/web/search-parameters)
  - 例えばフォルダだけ取得したい場合は```mimeType = 'application/vnd.google-apps.folder'```と指定する

### コード例

- CLIENT_ID,CLIENT_SECRET,REDIRECT_URL,REFRESH_TOKENは置き換える

```upload_file.js
var oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
oauth2Client.setCredentials({
  refresh_token: REFRESH_TOKEN
});

oauth2Client.refreshAccessToken(function(err,res){
  var drive = google.drive({ version: 'v2', auth: oauth2Client });
  drive.files.list({
    q: "title='kanban'"
  },function(err,res){

    // kanbanという名前のフォルダを探してIDを取得する
    var folderId = res.items[0].id

    var fs = require('fs');
    var file = fs.readFileSync('画像.jpg');

    // アップロードの日時からファイル名を作成
    var filename = new Date().toISOString() + ".jpg" 

    // ファイルのアップロード resourceでパラメータを指定するのがなかなかわからなかった
    drive.files.insert({
      auth: oauth2Client,
      resource: {
        title: filename,
        mimeType: 'image/jpeg',
        parents: [
          {
            id: folderId
          }
        ]
      },
      media: {
        mimeType: 'image/jpeg',
        body: file
      }
    }, function(err,res){
      if(err !== undefined && err !== null){
        console.error(err);
      }
      console.log(res)
      // 下記のURLでアップロードしたファイルに直接アクセスできるのはファイルのパーミッションがpublicの時のみ
      console.log('https://docs.google.com/a/ドメイン名/uc?id='+res.id);
    });
  });
});
```