🐥

RubyでSlackのfiles.getUploadURLExternal、completeUploadExternalを使う

に公開

files.upload APIを使っていたらSlackからfiles.upload APIは廃止になるから新しい形式に移行してねという連絡がきました。

Action required: Important notice about the files.upload API

めんどくさいなと思いながら対応しないとプログラムが動かなくなってしまうので軽い気持ちで対応したところ、罠があったので大変でした。

2025年11月12日に廃止予定だそうです。
https://api.slack.com/changelog/2024-04-a-better-way-to-upload-files-is-here-to-stay

どんな風に変わった?

今までfiles.uploadでファイルをアップロードすることができましたが、files.getUploadURLExternal をつかってアップロード先のURLを取得し(files.getUploadURLExternal)、そのURLに対してファイルをアップロードする(files.completeUploadExternal)二段階形式になりました。

https://api.slack.com/methods/files.getUploadURLExternal
https://api.slack.com/methods/files.completeUploadExternal

SDKを使わない方法

私はSDKを使って開発していなかったので自力で書くことにしました。

files.getUploadURLExternal

アップロード先のURLを取得します。

パラメータ名 内容
token SlackのAPIトークン
filename アップロードするファイル名
length アップロードするファイルのサイズ
  • ソースコード
require 'net/http'
require 'json'
require 'uri'

# Tokenは環境変数から読み出す
token = ENV['SLACK_TOKEN']
file_name = 'upload_test.png'

file = File.binread(file_name) # File.readだとダメ。

params = {
  length: file.size,
  token: token,
  filename: file_name
}

headers = {
  'Content-Type' => "application/x-www-form-urlencoded",
  'Authorization' => "Bearer #{token}"
}
uri = URI.parse "https://slack.com/api/files.getUploadURLExternal"

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.start do
  request = Net::HTTP::Post.new(uri.path, headers)
  request.set_form_data(params)
  http.request(request)
end
response_params = JSON.parse(response.body, symbolize_names:true)
p response_params
  • レスポンス
{:ok=>true, :upload_url=>"https://files.slack.com/upload/v1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", :file_id=>"XXXXXXXXXXXXX"}

files.completeUploadExternal

files.getUploadURLExternalで取得したURLに向けてアップロードします。

パラメータ名 内容
token SlackのAPIトークン
files "[{"id":"F044GKUHN9Z", "title":"slack-test"}]"←これ文字列

filesで本当に困ってました。公式のAPI仕様書を見てもarrayと書いてあるので配列渡せばいいのね。と思って配列で渡したらエラーが出るんですよね。調べてみると配列を文字列で渡せばよかったようです。

Array of file ids and their corresponding (optional) titles.

# 配列で渡したときのエラーレスポンス
{"ok"=>false, "error"=>"invalid_arguments", "response_metadata"=>{"messages"=>["[ERROR] must provide an object [json-pointer:/files/0]"]}}
  • 続きのコード
channel_id = ENV['SLACK_CHANNEL_ID']
uri = URI.parse response_params[:upload_url]
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.start do
  request = Net::HTTP::Post.new(uri.path, headers)
  request.body = file
  http.request(request)
end

params = {
  token: token,
  files: "[{'id': '#{response_params[:file_id]}'}]", # このダブルクォーテーションを外すとエラー。
  channel_id: channel_id
}

uri = URI.parse "https://slack.com/api/files.completeUploadExternal"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new uri
req.set_form_data(params.merge({token: token}))
res = http.request(req)
response_body = JSON.parse(res.body)
p response_body

ファイルが中途半端にアップロードされる

上のコードを書けば中途半端にアップロードされることはないのですが、最初書いたときはFile.readでローカルのファイルを読みだしてfiles.getUploadURLExternalのリクエストパラメータ「length」をFile#sizeを使って指定していました。
これをやったところ中途半端しかないファイルがアップロードされてしまいました。File.readの仕様を確認したところ「引数 length が指定された場合はバイナリ読み込みメソッド、そうでない場合はテキスト読み込みメソッドとして動作します。」と書いてありました。テキストモードで読み込んだ結果適切にサイズが計算できていなかったみたいなので、File#binreadでバイナリモードで読み込むように修正しました。

まとめ

files.completeUploadExternalの引数とFileのサイズではまってました。APIの向き先変えるだけだと思ったら、想定よりも時間がかかったので私みたいにはまる人が今後出ないことを祈ります。

Discussion