【GAS × LINE WORKS】Bot で受け取ったファイルを Google Drive に保存する

Google Apps Script Advent Calendar 2020 の 18日目！
どうぞよろしくお願いします。

# はじめに

リモートワークでチャットの利用が増えてきた昨今。
仕事も効率よく自動化するために Bot の利用も増えてきました。

報告用 Bot とか経費精算 Bot とかで、メンバーがファイルを送信したら Google Drive に保存して管理者に通知してくれたら管理しやすいですよね。

ってなわけで作ってみました！(*'▽')

# 実際の画面

**ユーザ画面**
![1608014275.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/0b2f867b-6d1f-2d68-96da-6ce5676b11c1.png)

**管理者画面**
![1608014243.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/34916a32-b220-50f2-6b93-23c110649380.png)

**Google Drive**
![1608014346.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/20876564-77e8-4781-7dd4-295034fe2e3e.png)

# GoogleAppsScript で LINE WORKS BOT を作る

以下の記事を参考に Bot を作成してあります。
[【GAS × LINE WORKS】BOT で PDF ファイルを送受信！](https://qiita.com/kunihiros/items/4e96990d9f32cecc1fa5)

この記事で作った Bot にコードを書き加えていきます♪

### コードを書き加える

ファイルデータを受信するプログラムは実装されているので、追加で実装する機能は以下の２つです。

1. 受信したファイルをダウンロードする
2. ダウンロードしたファイルを Google Drive に保存する

ではでは、ひとつずつ実装していきまっす！ヾ(*´∀｀*)ﾉ

#### 1. 受信したファイルをダウンロードする

Bot がファイルを受信すると `resourceId` というものを取得します。
実際のファイルデータを手に入れるには、この `resourceId` を使って API を Request します。
使用する API は[トーク Bot のコンテンツダウンロード API](https://developers.worksmobile.com/jp/document/1005026?lang=ja) です。

では、この API を呼び出す関数を作っていきます。

```js:fileDownload
function fileDownload(resourceId) {
  const headers = {
    'consumerKey': CONSTS.consumerKey,
    'Authorization': 'Bearer ' + LINEWORKS.getToken(CONSTS),
    'x-works-apiid': CONSTS.apiId,
    'x-works-resource-id': resourceId
  }  
  const options = {
    'method': 'get',
    'headers': headers
  }
  const uri = 'http://storage.worksmobile.com/openapi/message/download.api'
  return UrlFetchApp.fetch(uri, options)
}
```

`headers` には認証情報が入ります。
`CONSTS` への認証情報のセットについては[コチラ](https://qiita.com/kunihiros/items/4e96990d9f32cecc1fa5#%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F)をご参考ください。
ダウンロードするファイルの `resourceId` も `headers` に記載します。

ダウンロードしたファイルは `HTTPResponse` オブジェクトで取得されます。
[公式 Reference - Class HTTPResponse](https://developers.google.com/apps-script/reference/url-fetch/http-response)

あっという間ですね！( ﾟДﾟ)

では、`HTTPResponse` オブジェクトを Google Drive に保存していきましょう。

#### 2. ダウンロードしたファイルを Google Drive に保存する

`HTTPResponse` オブジェクトの操作は簡単。
`.getBlob()` で作成した `Blob` オブジェクトを `DriveApp` で書き込むだけ・・・

と、思いきや意外な落とし穴がありました。
保存したファイルの名前がなぜかすべて `download.api` になってしまうのです！
呪いか？( ﾟДﾟ)

![1608015703.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/fa993a37-a6c5-a1f9-559d-184eb4560a38.png)

何故だかはわかりませんが、ファイル名が正常に取れない模様。
仕方がないので `HTTPResponse` のヘッダー情報からファイル名を取得することにしました。

```js:createFile
function createFile(response){
  const resHeaders = response.getAllHeaders()
  const refererUri = resHeaders['x-hlink-org-referer'].split('/')
  const filename = refererUri[refererUri.length - 2]
  const resBlob = response.getBlob()
  resBlob.setName(filename)
  const drive = DriveApp.getFolderById(folderId)
  drive.createFile(resBlob)
}
```

`filename` が一発で取れれば良かったのですが、無理なので `split()` しました。
ちなみにヘッダー情報の中身はこんな構成でした。ご参考までに。

```
{x-resource-type=resource, Date=Tue, 15 Dec 2020 05:52:01 GMT, Last-Modified=Tue, 15 Dec 2020 05:25:28 GMT, x-hlink-org-referer=https://jp1-storage.worksmobile.com/k/oneapp/r/jp1.xxxxxxxx/xxxxxxxx/v2-xxxxxxxx-443220668.190531528/TEST_PDF.pdf/, ETag=1608009928, Server=FileCloud, Connection=keep-alive, Content-Disposition=attachment; filename="TEST_PDF.pdf"; charset=utf-8; filename*=UTF-8''TEST_PDF.pdf, Content-Length=28315, Content-Type=application/pdf}
```

無事に実行され、ファイルが Google Drive に保存されましたとさー(*‘∀‘)

![1608014346.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/e1cf9d06-edd5-563c-0fbf-1cf43980d3b9.png)

おしまい♪

# 全体のコード

<details><summary>全体のコードはここに折りたたんで貼り付けておきます。</summary><div>

```js:main.gs
// LINE WORKS API 認証情報
const CONSTS = {
    'apiId': 'API ID',
    'consumerKey': 'Server API Consumer Key',
    'serverId': 'Server List(ID登録タイプ) の ID',
    'privateKey': 'Server List(ID登録タイプ) の認証キー',
    'botNo': 'botNo',
    'botMaster': 'BOT 管理者のアカウント ID'
}
// ファイルを保存するフォルダの ID
const folderId = 'xxxxxxxxxxxxxxxxxxxxxxxxx'

// BOT 
function doPost(e) {
  if(e === null || e.postData === null || e.postData.contents === null) return
  const requestJSON = e.postData.contents
  const requestObj = JSON.parse(requestJSON)
  const user = requestObj.source.accountId
  switch(requestObj.content.type){
    case 'file':
      const resourceId = requestObj.content.resourceId
      LINEWORKS.sendMsg(CONSTS, CONSTS.botMaster, `${user}さんからファイルが届きました。`)
      LINEWORKS.sendFile(CONSTS, CONSTS.botMaster, resourceId)
      LINEWORKS.sendMsg(CONSTS, user, '管理者に File を転送しました。')
      const file = fileDownload(resourceId)
      createFile(file)
      LINEWORKS.sendMsg(CONSTS, CONSTS.botMaster, 'Google Drive に File を保存しました。')
      break
    default:
      LINEWORKS.sendMsg(CONSTS, user, '管理者に File を送信するにはファイルをトークに添付してください。')
  }
}
/**
 * コンテンツダウンロード API を使用してファイルを取得する
 * @param {string} resourceId LINE WORKS からダウンロードするファイルの resourceId
 * @return {HTTPResponse object} ダウンロードしたファイルデータ
 */
function fileDownload(resourceId) {
  const headers = {
    'consumerKey': CONSTS.consumerKey,
    'Authorization': 'Bearer ' + LINEWORKS.getToken(CONSTS),
    'x-works-apiid': CONSTS.apiId,
    'x-works-resource-id': resourceId
  }  
  const options = {
    'method': 'get',
    'headers': headers
  }
  const uri = 'http://storage.worksmobile.com/openapi/message/download.api'
  return UrlFetchApp.fetch(uri, options)
}

/**
 * ダウンロードしたファイルデータを folderId のフォルダに保存する
 * @param {HTTPResponse object} ダウンロードしたファイルデータ
 */
function createFile(response){
  const resHeaders = response.getAllHeaders()
  const refererUri = resHeaders['x-hlink-org-referer'].split('/')
  const filename = refererUri[refererUri.length - 2]
  const resBlob = response.getBlob()
  resBlob.setName(filename)
  const drive = DriveApp.getFolderById(folderId)
  drive.createFile(resBlob)
}
```

</div></details>

# おわりに

ここまでお付き合いいただきありがとうございました。

前回の宣言通り、Google Drive に保存してやったぜ！(*‘∀‘)ﾒｽﾞﾗｼｲ
`UrlFetchApp.fetch()` からのファイル保存は意外と簡単にできるんですね。
やっぱり GoogleAppsScript は扱いやすいなー。
連携もしやすいし、LINE WORKS API との相性いいかも？

あと、Advent Calender の記事を色々と見ていたら @taketakekaho さんの記事で Google Drive の OCR 機能が無料で使えることを知りました( ﾟДﾟ)
[GASとGoogleDriveのOCR機能で文字起こしボットを作ってみた時のTips](https://qiita.com/taketakekaho/items/17d8fc79d9a423e4351b#google-drive%E3%81%AEocr%E3%81%8C%E7%84%A1%E6%96%99%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B)

次はさらに OCR 機能追加してみるのも楽しいかも！

ではまた！(^^)/

# 参考にさせていただきましたm(_ _)m

[LINEWORKS Developers](https://developers.worksmobile.com/jp/document?lang=ja)
[公式 Reference - Class HTTPResponse](https://developers.google.com/apps-script/reference/url-fetch/http-response)