files.upload API 廃止におけるGASの改修

# きっかけ

最近Slackで新しいアプリを作ってテストしていたところ
Slack APIのfiles.uploadエンドポイントで以下のエラーが返却されうまくいきませんでした。
```
{'ok': False, 'error': 'method_deprecated'}
```
原因がさっぱりわからなくてググってみたところ以下の記事を偶然見かけました。

https://zenn.dev/slack/articles/7ce5065cc4daa7

特に重要だったのが以下の内容です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123439/c59accf0-ba52-ed80-8efe-a2a1a96b8c1b.png)
昔作ったアプリでは動作していたので変だなと思っていたら
この条件に該当していたことが判明しました。

# 詳細

SDKが提供されているプラットフォームであれば既存メソッド名にv2を付与すれば利用できるようですが
GASではスクラッチで実装しているのでこの方法は不可能でした。
以下の手順を踏む必要がありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123439/0942104b-35b6-0c32-015a-30bca278fe4e.png)

最初にファイルアップロード専用のURLを取得してそこにファイル実体をアップロードし処理のクロージング通知をしてあげる必要があるみたいです。
以下の関数では一つのファイルをアップロードしてSlackに投稿するサンプル関数です。（ほぼChatGPT作）

```javascript
/**
 * Slackにファイルをアップロードする関数
 * @param {string} name - ファイル名
 * @param {Blob} fileBlob - アップロードするファイルのBlob
 * @param {string} token - SlackのAPIトークン
 * @param {string} channel - ファイルを投稿するチャンネルID
 */
function uploadFileToSlack(name, fileBlob, token, channel) {
  // 1. ファイルのサイズを取得
  var fileSize = fileBlob.getBytes().length;

  // 2. files.getUploadURLExternal APIを呼び出してURLとファイルIDを取得
  var uploadUrlResponse = UrlFetchApp.fetch(`https://slack.com/api/files.getUploadURLExternal?filename=${name}&length=${fileSize}`, {
    method: "get",
    headers: {
      "Authorization": "Bearer " + token,
    }
  });
  
  var uploadUrlJson = JSON.parse(uploadUrlResponse.getContentText());
  if (!uploadUrlJson.ok) {
    throw new Error("Failed to get upload URL: " + uploadUrlJson.error);
  }
  
  var uploadUrl = uploadUrlJson.upload_url;
  var fileId = uploadUrlJson.file_id;

  console.log(uploadUrl)
  console.log(fileId)

  // 3. 取得したURLにファイルをアップロード
  var uploadResponse = UrlFetchApp.fetch(uploadUrl, {
    method: "post",
    payload: {
      token: token,
      file: fileBlob,
      filename: name
    }
  });

  if (uploadResponse.getResponseCode() !== 200) {
    throw new Error("Failed to upload file: " + uploadResponse.getContentText());
  }

  // 4. files.completeUploadExternal APIにファイルIDとオプション引数を送信してアップロードを完了
  var completeUploadPayload = {
    files: [{ id: fileId, title: name }]
  };

  if (channel) {
    completeUploadPayload.channel_id = channel;
  }

  var completeUploadResponse = UrlFetchApp.fetch("https://slack.com/api/files.completeUploadExternal", {
    method: "post",
    headers: {
      "Authorization": "Bearer " + token,
      "Content-Type": "application/json"
    },
    payload: JSON.stringify(completeUploadPayload)
  });

  var completeUploadJson = JSON.parse(completeUploadResponse.getContentText());
  if (!completeUploadJson.ok) {
    throw new Error("Failed to complete upload: " + completeUploadJson.error);
  }

  console.log(completeUploadJson);
  return completeUploadJson;
}
```
[files.completeUploadExternal](https://api.slack.com/methods/files.completeUploadExternal)にはさらにinitial_commentやthread_tsといったパラメータもありますのでアップロードファイルにメッセージを付与したりスレッド内に投稿したりするこも可能です。
サンプルソースですと以下のような感じでパラメータを拡張してあげます。
```javascript
completeUploadPayload.channel_id = channel;
```


ちなみに旧バージョンの関数はこちらです。

```javascript
/**
 * Slackにファイルをアップロードする関数
 * @param {string} name - ファイル名
 * @param {Blob} fileBlob - アップロードするファイルのBlob
 * @param {string} token - SlackのAPIトークン
 * @param {string} channel - ファイルを投稿するチャンネルID
 */
function uploadFileToSlack(name, fileBlob, token, channel) {

  var url = 'https://slack.com/api/files.upload';
  var form = {
   token: token,
   title: name,
   filename: "filename",
   filetype: "auto",
   channels: channel,
   file: fileBlob
  };

  var options = {
    method: 'post',
    payload: form,
  };

  UrlFetchApp.fetch(url, options);
}
```