【GAS】Slackでのファイルアップロード方法の変更(files.upload廃止対応版)

当サイトではアフィリエイト広告を利用して商品を紹介しています。

SlackのAPIのfiles.uploadのエンドポイントが非推奨になるというアナウンスが有りました。
今回はこれに伴って「files.getUploadURLExternal」「files.completeUploadExternal」この2つのエンドポイントを使用してファイルをアップロードし、メッセージを投稿する方法を解説します。

公式アナウンスより

files.upload is deprecated and will stop functioning on March 11, 2025. Use files.getUploadURLExternal and files.completeUploadExternal to upload files instead. Newly created apps will be unable to use files.upload beginning May 8, 2024. See Uploading files for more details on the process and this changelog for more on the deprecation.

files.upload は非推奨となり、2025年3月11日に機能しなくなります。ファイルのアップロードには files.getUploadURLExternalfiles.completeUploadExternal を使用してください。2024年5月8日以降に作成されたアプリは files.upload を利用できません。プロセスの詳細やこの非推奨に関する変更ログについては「ファイルのアップロード」をご覧ください。

https://api.slack.com/methods/files.upload

ファイルアップロードの概要

files.uploadのエンドポイントでは、このエンドポイントを使用することでファイルアップロードとメッセージの投稿を一度に行うことが可能でした。
しかし、廃止後は以下のステップでファイルとメッセージを投稿する必要があります。

  1. files.getUploadURLExternalのエンドポイントを使用して、アップロードURLとファイルのIDを取得する
  2. 取得したURLに対してファイルをアップロードする
  3. files.completeUploadExternalのエンドポイントを使用して、ファイルとメッセージを投稿する

トークンの権限

サンプルのコードを使用するのにはSlackのBOTを使用します。
この記事ではBOTそのものの作成方法は解説しません。
トークンの権限についてはファイルを扱うため
Bot tokensであれば「files:write」
User tokensであれば「files:write」「files:write:user」
このトークンが必要になります。

トークンはプロパティストアにSLACK_TOKENに保存しておきます。

サンプルコード

function uploadToSlack() {
  const imageUrl01 = "https://so.sha-box.com/wp-content/uploads/2024/04/%E3%83%96%E3%83%AD%E3%82%B0%E3%82%B5%E3%83%A0%E3%83%8D-3-300x169.png";
  const response01 = UrlFetchApp.fetch(imageUrl01);
  const fileBlob01 = response01.getBlob().setName("logo01.png");

  const imageUrl02 = "https://so.sha-box.com/wp-content/uploads/2022/01/student-ga1eef4fe9_1920-150x150.jpg";
  const response02 = UrlFetchApp.fetch(imageUrl02);
  const fileBlob02 = response02.getBlob().setName("logo02.png");

  const fileBlobs = [fileBlob01, fileBlob02]

  const channelId = 'チャンネルID'; // SlackのチャンネルID
  const uploader = new SlackFileUploader();
  uploader.post(fileBlobs, channelId, 'こちらがアップロードされたファイルです');
}

/**
 * Slackにファイルをアップロードするためのクラス。
 * @class
 */
class SlackFileUploader {
  /**
   * SlackFileUploaderを作成します。
   * @param {string} [token] - Slack APIトークン。省略された場合はプロパティストアから取得します。
   */
  constructor(token) {
    this.token = token || PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN');
    if (!this.token) {
      throw new Error('Slack APIトークンが必要です。');
    }
  }

  /**
   * 複数のファイルとコメントをSlackに投稿します。
   * @param {GoogleAppsScript.Base.Blob[]} fileBlobs - アップロードするファイルの配列。
   * @param {string} channelId - 共有するチャンネルのID。
   * @param {string} [initial_comment] - アップロードされたファイルに対する初期コメント(省略可能)。
   * @param {string} [thread_ts] - スレッドのタイムスタンプ(省略可能)。
   */
  post(fileBlobs, channelId, initial_comment, thread_ts) {
    // ファイルIDの配列を生成
    const files = fileBlobs.flatMap(fileBlob => {
      const fileName = fileBlob.getName();
      const fileSize = fileBlob.getBytes().length;
      const uploadUrlData = this.getUploadURL(fileName, fileSize);

      if (!uploadUrlData.ok) {
        Logger.log('Failed to retrieve upload URL');
        return [];
      }

      const uploadResult = this.uploadFile(uploadUrlData.upload_url, fileBlob);
      if (!uploadResult) {
        Logger.log('Failed to upload file');
        return [];
      }
      const fileId = uploadUrlData.file_id
      const fileObject = {
        id: fileId,
        title: fileName
      }
      return fileObject;
    })

    // 全てのファイルが正常にアップロードされた場合のみ、finalizeUploadを呼び出す
    if (files.length > 0) {
      const finalizeResult = this.finalizeUpload(files, channelId, initial_comment, thread_ts);
      Logger.log(finalizeResult);
    }
  }

  /**
   * Slackからファイルアップロード用のURLを取得します。
   * @param {string} fileName - アップロードするファイルの名前。
   * @param {number} fileSize - アップロードするファイルのサイズ(バイト単位)。
   * @return {Object} アップロードURLとファイルIDを含むオブジェクト。
   */
  getUploadURL(fileName, fileSize) {
    const url = `https://slack.com/api/files.getUploadURLExternal?token=${encodeURIComponent(this.token)}&filename=${encodeURIComponent(fileName)}&length=${fileSize}`;
    const response = UrlFetchApp.fetch(url, {
      method: 'get',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      muteHttpExceptions: true
    });
    return JSON.parse(response.getContentText());
  }

  /**
   * 指定されたURLにファイルをアップロードします。
   * @param {string} uploadUrl - アップロードするURL。
   * @param {GoogleAppsScript.Base.Blob} fileBlob - アップロードするファイル。
   * @return {string} アップロードのレスポンステキスト。
   */
  uploadFile(uploadUrl, fileBlob) {
    const response = UrlFetchApp.fetch(uploadUrl, {
      method: 'post',
      payload: fileBlob.getBytes(),
      muteHttpExceptions: true
    });
    return response.getContentText();
  }

  /**
   * アップロードを完了し、ファイルを指定されたチャンネルに共有します。
   * @param {Array} files - アップロードされたファイルのIDとオプショナルなタイトルを含むオブジェクトの配列。
   * @param {string} channelId - 共有するチャンネルのID。
   * @param {string} [initial_comment] - アップロードされたファイルに対する初期コメント(省略可能)。
   * @param {string} [thread_ts] - スレッドのタイムスタンプ(省略可能)。
   * @return {Object} ファイルアップロードの最終ステップの結果。
   */
  finalizeUpload(files, channelId, initial_comment, thread_ts) {
    const payload = {
      files: JSON.stringify(files),
      channel_id: channelId,
    };

    if (initial_comment) {
      payload.initial_comment = initial_comment;
    }
    if (thread_ts) {
      payload.thread_ts = thread_ts;
    }

    const response = UrlFetchApp.fetch('https://slack.com/api/files.completeUploadExternal', {
      method: 'post',
      headers: {
        'Authorization': 'Bearer ' + this.token,
        'Content-Type': 'application/json; charset=utf-8'
      },
      payload: JSON.stringify(payload),
      muteHttpExceptions: true
    });
    return JSON.parse(response.getContentText());
  }
}

使い方・準備

サンプルコードをすべてコピーしたらchannelId を自分のSlackの環境に合わせて書き換えてください。
そのままuploadToSlackを実行してください。
サンプルコードをすべてコピーしたらchannelId を自分のSlackの環境に合わせて書き換えてください。
そのままuploadToSlackを実行してください。
以下のように投稿されたら成功です

参考リンク

files.upload
files.getUploadURLExternal
files.completeUploadExternal
Working with files