Google Apps Scriptを使ってmultipart/form-dataタイプでHTTPリクエストする

# 背景
先日、Google Apps Scriptの[UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)を使って``multipart/form-data``タイプでファイルをやり取りする状況が発生しました。オフィシャルドキュメントでは、payloadにオブジェクトを与えると自動で``application/x-www-form-urlencoded``または``multipart/form-data``としてリクエストされるとありましたが、残念ながら今回の用途には使用できませんでした。また、Javascriptのfetchで使用する[Form​Data()](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData)のようなサンプルがあればと探しましたが残念ながら見つからず、丁度いくつかのアプリで使用する予定もありましたのでライブラリとして作成しました。リポジトリは[こちら](https://github.com/tanaikech/FetchApp)です。

# ライブラリのプロジェクトキー
~~~
1sm9V-w8-0i3U4-10N6XyaRjHk5voiuJ1ArKSLo3htOUasB6GcPcIq8Kb
~~~

# インストール方法
1. 最初にスクリプトディタを立ち上げてライブラリをインストールします。インストール方法は[こちら](https://developers.google.com/apps-script/guides/libraries)をご覧ください。
    - このとき使用するライブラリのプロジェクトキーは、**``1sm9V-w8-0i3U4-10N6XyaRjHk5voiuJ1ArKSLo3htOUasB6GcPcIq8Kb``**です。

# 使い方
使用方法は、サンプルスクリプトをご覧頂く方が分かりやすいのではないかと思いましたので、サンプルスクリプトを使用します。

## サンプルスクリプト 1
このスクリプトでは、単体のリクエストを実行しています。基本は[``UrlFetchApp.fetch()``](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchurl-params)と同じですが、formDataを与えるbodyのプロパティを追加しています。

### スクリプトの動作内容
Drive API v3を使用してPDFファイルあるいは画像ファイルをOCRを行いGoogle Documentに変換します。

### スクリプト
~~~javascript
function sample1() {
  var fileId = "### fileId of PDF ###";
  var metadata = {
    name: "sampleDocument", // Filename of created Google Document
    mimeType: MimeType.GOOGLE_DOCS // MimeType of Google Document
  };
  var fileBlob = DriveApp.getFileById(fileId).getBlob();
  var form = FetchApp.createFormData(); // Create form data
  form.append(
    "metadata",
    Utilities.newBlob(JSON.stringify(metadata), "application/json")
  );
  form.append("file", fileBlob);
  var url =
    "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart";
  var params = {
    method: "POST",
    headers: { Authorization: "Bearer " + ScriptApp.getOAuthToken() },
    body: form
  };
  var res = FetchApp.fetch(url, params);
  Logger.log(res);
  // DriveApp.createFile(blob) // This comment line is used for automatically detecting scope for running this sample script.
}
~~~

## サンプルスクリプト 2
このスクリプトでは、複数のリクエストを一度に実行しています。基本は[``UrlFetchApp.fetchAll()``](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchallrequests)と同じですが、formDataを与えるbodyのプロパティを追加しています。

### スクリプトの動作内容
Drive API v3を使用してテキストファイルを既存のGoogle Documentに上書きします。リクエスト自体は[非同期で実行](https://gist.github.com/tanaikech/c0f383034045ab63c19604139ecb0728)されます。Drive APIを用いたファイルのアップデートはバッチリクエストでは実行できないことを考えると、下記のようなスクリプトはAPI使用回数はリクエスト数と同数にはなりますが、現状の一つの回避策になるかもしれません。

### スクリプト
~~~javascript
function sample2() {
  var contents = [
    {
      fileName: "newFilename1", // new filename
      docs: "### GoogleDocumentId1 ###", // Destination fileId of existing Google Document.
      textFile: "### textFileId1 ###" // Source fileId of text file.
    },
    {
      fileName: "newFilename2",
      docs: "### GoogleDocumentId2 ###",
      textFile: "### textFileId2 ###"
    }
  ];
  var accessToken = ScriptApp.getOAuthToken();
  var requests = contents.map(function(e) {
    var metadata = { name: e.fileName };
    var form = FetchApp.createFormData(); // Create form data
    form.append(
      "metadata",
      Utilities.newBlob(JSON.stringify(metadata), "application/json")
    );
    form.append("file", DriveApp.getFileById(e.textFile).getBlob());
    var url =
      "https://www.googleapis.com/upload/drive/v3/files/" +
      e.docs +
      "?uploadType=multipart";
    params = {
      url: url,
      method: "PATCH",
      headers: { Authorization: "Bearer " + accessToken },
      body: form
    };
    return params;
  });
  var res = FetchApp.fetchAll(requests);
  Logger.log(res);
  // DriveApp.createFile(blob) // This comment line is used for automatically detecting scope for running this sample script.
}
~~~