Google Driveにcurlを使ってファイルのアップロードやダウンロードを行う

なぜcurlなのか

公式で提供されているクライアントライブラリを利用するのがお手軽かと思います。

今回は実行するマシン(主にMac)でライブラリのセットアップを必要とせず、そのまま実行できることを目的としてcurlを選びました。

Google Drive APIを実行するために

各種APIを実行するにはアクセストークンが必要になります。

OAuth 2.0クライアントの作成

  1. Google Cloud Platform (GCP)でプロジェクトを作成。

  2. 有効なAPIとサービスより、Google Drive APIを有効化。

  3. 認証情報を作成より、OAuth クライアントIDを選択。

  4. ウェブアプリケーション、リダイレクトURLをhttp://localhost:8080を指定して作成。

  5. 作成後、クライアントIDシークレットIDを控える。

Authorization Codeを取得

認証を行うURLを作成し、ブラウザでGoogle Driveの操作を行うアカウントからOAuth認証します。

パラメータ
client_id 先ほど取得した値
redirect_uri https://localhost:8080
scope 権限
スコープの値はコチラを参照。
ファイルアップロード/ダウンロードであれば2つで十分です。
https://www.googleapis.com/auth/drive : Google ドライブのすべてのファイルの表示、編集、作成、削除を行えます
https://www.googleapis.com/auth/drive.metadata : Google ドライブ内のファイルのメタデータの表示と管理
response_type code
access_type offline

組み立て

https://accounts.google.com/o/oauth2/auth?client_id=[クライアントID]&
redirect_uri=https://localhost:8080&
scope=https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.metadata&
response_type=code&
access_type=offline

アクセスを許可する。

localhostなのでアクセスできません となりますが、アドレスバーからAuthorization Codeを確認できます。

http://localhost:8080/?code=[Authorization Code]&
scope=https://www.googleapis.com/auth/drive%20https://www.googleapis.com/auth/drive.metadata

リフレッシュトークンを取得

先ほど取得したAuthorization Codeを使ってリフレッシュトークンを取得します。

curl 'https://www.googleapis.com/oauth2/v4/token' \
--data 'code=[Authorization Code]' \
--data 'client_id=[クライアントID]' \
--data 'client_secret=[シークレットID]' \
--data 'redirect_uri=http://localhost:8080' \
--data 'grant_type=authorization_code' \
--data 'access_type=offline'

レスポンス

{
  "access_token": "ya29.*****",
  "expires_in": 3599,
  "refresh_token": "1//*****",
  "scope": "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.metadata",
  "token_type": "Bearer"
}

アクセストークンとリフレッシュトークンが得られます。
アクセストークンは1時間で有効期限が切れるので、リフレッシュトークンを使って再取得します。

アクセストークンを取得

curl 'https://www.googleapis.com/oauth2/v4/token' \
--data refresh_token=[リフレッシュトークン] \
--data client_id=[クライアントID] \
--data client_secret=[シークレットID] \
--data grant_type=refresh_token

レスポンス例

{
  "access_token": "*****",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.metadata",
  "token_type": "Bearer"
}

有効期限が切れた状態でAPIを実行するとエラーコード 401 が返るので、再度アクセストークンを取得し直してください。

{
  "error": {
    "code": 401,
    "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "errors": [
      {
        "message": "Invalid Credentials",
        "domain": "global",
        "reason": "authError",
        "location": "Authorization",
        "locationType": "header"
      }
    ],
    "status": "UNAUTHENTICATED"
  }
}

アップロード

アップロード先が共有ドライブの場合、supportsAllDrives=trueを指定する。
マイドライブの場合はfalseでも可。

curl -X POST 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true' \
-F "metadata={name:'[ファイル名]', mimeType:'application/octet-stream', parents:['[ドライブID]']};type=application/json;charset=UTF-8" \
-F 'file=@'[ファイルパス]';type=application/octet-stream' \
-H 'Authorization: Bearer [トークン]'

レスポンス例

{
  "kind": "drive#file",
  "id": "[ファイルID],
  "name": "hoge.png",
  "mimeType": "image/png"
}

ダウンロード

-oオプションでファイル保存。
ドライブ上と同じファイル名が不明で同名で保存したい場合、メタデータを別途取得する必要がある。

curl -X GET 'https://www.googleapis.com/drive/v3/files/[ファイルID]?alt=media' \
-o [ファイル名] \
-H "Accept: application/json" \
-H "Authorization: Bearer [トークン]"

ファイルメタデータ

共有ドライブ内のファイルの場合、supportsAllDrives=trueを指定。

curl -X GET 'https://www.googleapis.com/drive/v3/files/[ファイルID]?supportsAllDrives=true' \
-H "Accept: application/json" \
-H "Authorization: Bearer [トークン]"

レスポンス例

{
  "kind": "drive#file",
  "id": "[ファイルID]",
  "name": "[ファイル名]",
  "mimeType": "text/plain",
  "teamDriveId": "[チームドライブID]",
  "driveId": "[ドライブID]"
}

フォルダ内ファイルの列挙

少し複雑、クエリを使用します。
デフォルトだとゴミ箱に移動したファイルも列挙されるため、trashed=falseで除外する。
共有ドライブの場合、supportsAllDrivesincludeItemsFromAllDrivesの両方をtrueに指定する。

curl -X GET 'https://www.googleapis.com/drive/v3/files?q='"'"'[フォルダID]'"'"'+in+parents+and+trashed=false&supportsAllDrives=true&includeItemsFromAllDrives=true' \
-H "Authorization: Bearer [トークン]"

レスポンス例

{
  "kind": "drive#fileList",
  "incompleteSearch": false,
  "files": [
    {
      "kind": "drive#file",
      "driveId": "[ドライブID]",
      "mimeType": "text/plain",
      "id": "[ファイルID]",
      "name": "[ファイル名]",
      "teamDriveId": "[チームドライブID]"
    },
    {
      "kind": "drive#file",
      "driveId": "[ドライブID]",
      "mimeType": "text/plain",
      "id": "[ファイルID]",
      "name": "[ファイル名]",
      "teamDriveId": "[チームドライブID]"
    },
    {}
  ]
}

ページング

pageSizeを指定しない場合は最大100件のファイルを列挙しますが、
超える場合はnextPageTokenというフィールドが追加されます。

{
  "nextPageToken": "[ページトークン]",
  "kind": "drive#fileList",
  "incompleteSearch": false,
  "files": [
    {
      "kind": "drive#file",
      "driveId": "[ドライブID]",
      "mimeType": "text/plain",
      "id": "[ファイルID]",
      "name": "[ファイル名]",
      "teamDriveId": "[チームドライブID]"
    },
    {},
  ]
}

全ファイルを列挙する際はnextPageTokenが無くなるまで順次実行します。

※ MacのZshで実行する際のハマりポイント
ページトークンは先頭 ~!!~から始まるのですが、ヒストリー展開されてエラーになることがありました。
なのでURL全体はダブルクォートではなく、シングルクォートでエスケープするようにしています。

curl -X GET 'https://www.googleapis.com/drive/v3/files?q='"'"'[フォルダID]'"'"'+in+parents+and+trashed=false&supportsAllDrives=true&includeItemsFromAllDrives=true&pageToken=[ページトークン]' \
-H "Authorization: Bearer [トークン]"