Google GAS でスプレッドシートを Excel に変換して Webex Teams でつぶやく multipart/form-data のサンプル

# はじめに
この記事は、GAS で Webex Teams bot を作る記事の続編になります。Webex Teams bot の作り方は、下記の記事を参考にしてください。
https://qiita.com/hiroactivity/items/7e319de7821bba36ba4a
ここでは bot に Excel を Multipart で添付して送るサンプルコードを紹介します。

# 実際の動作
## まずはコードの紹介
手っ取り早くコピペで作りたい方のためにまずはコードを紹介します。下記のような感じです。

```js
function sendSpreadsheetAsExcel(){
  // Google Spreadsheet を Excel で取得して Webex に添付してつぶやきます

  // Webex 関連
  const webexToken = "<bot token>"; 
  const webexRoom = "<roomId>"; 
  const webexUrl = 'https://webexapis.com/v1/messages/';

  // google spreadsheet の ID, URL の /d/<この値>/edit? を抜き出す
  const spreadsheetId = 'abcabcabcabc';

  // spreadsheet を Excel で取得するための URL
  const fetchUrl = "https://docs.google.com/feeds/download/spreadsheets/Export?key="+
    spreadsheetId+"&amp;exportFormat=xlsx";

  // OAuth2 設定
  const fetchOpt = {
    "headers": {Authorization: "Bearer " + ScriptApp.getOAuthToken()},
    "muteHttpExceptions": true
  };

  // Excel を blob で取得
  const xlsxFile = UrlFetchApp.fetch(fetchUrl, fetchOpt).getBlob();

  // multipart/form-data のための boundary やヘッダ設定
  const boundary = "myBoundary";

  const headers = {
    'Authorization': 'Bearer ' + webexToken,
    'Content-Type': "multipart/form-data; boundary=" + boundary
  };
  
  // multipart/form-data を作成
  const payload = Utilities.newBlob(
    '--'+boundary+'\r\nContent-Disposition: form-data; name="roomId"\r\n\r\n'+webexRoom+'\r\n' +
    '--'+boundary+'\r\nContent-Disposition: form-data; name="text"\r\n\r\nファイルです！\r\n' +
    '--'+boundary+'\r\nContent-Disposition: form-data; name="files"; filename="myexcel.xlsx"\r\n'+  
        'Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n').getBytes()
    .concat(xlsxFile.getBytes())
    .concat(Utilities.newBlob('\r\n\r\n--'+boundary+'--\r\n').getBytes()
  );

  const options = {
    'method': 'POST',
    'muteHttpExceptions': true,
    'headers': headers,
    'payload': payload
  };

  res = UrlFetchApp.fetch(webexUrl, options);
  console.log(res.toString());
}
```
## 動作結果
上記コードに Bot token, Room ID, Google spreadsheet ID を埋めて GAS で動かしていただくと、下記のように Webex Teams にファイルを添付してつぶやいてくれるはずです。

![Screenshot 2024-12-03 at 20.37.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156216/fc1fb7ed-bd75-d700-5ddc-655e10c34df0.png)


# 解説
## multipart/form-data
今回のメインである GAS で multipart/form-data をどのように送るかの解説です。
Webex API のガイドを見ると、下記のような Python のコードが掲載されています。

参考: https://developer.webex.com/docs/basics

```python
m = MultipartEncoder({'roomId': 'Y2lzY2.....',
                      'text': 'example attached',
                      'files': ('example.png', open('example.png', 'rb'),
                      'image/png')})
```

今回 GAS で同じことをしようと思ったのですが、GAS では MultipartEncoder のような便利なものは（おそらく）存在しなかったので、自分で作ってみたというのがこの記事の内容になります。ただ上記の Python ではどのように multipart になっているのかわからず、一度 Python でコードを書いて解析しました。その結果、下記のようになっていれば良いようです。これに沿って作ったのが、今回の GAS のコードになります。


```
--boundary\r\nContent-Disposition: form-data; name="roomdId"<roomId>\r\n
--boundary\r\nContent-Disposition: form-data; name=text"テキストメッセージ\r\n
--boundary\r\nContent-Disposition: form-data; name="files"; filename="任意のファイル名" \r\n
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n
<ファイルの blob>
\r\n\r\n--boundary--\r\n
```

ここで、ファイルの blob の後に \r\n を入れるのが重要です。最初これを忘れていてかなりハマりました。