1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Slackのfiles.getUploadURLExternalでどハマりした

Last updated at Posted at 2024-06-07

Slackから「files.uploadなくなるよー」って連絡が来たので「API変えればいいんでしょ?よゆーっす。」と思って対応したらえらい大変だったので誰かのお役に立てばと思い投稿しました。

最終的なコードは正解のコードセクションにありますので、細かいことはいいから答えだけ知りたい方はそちらを見てください。

以下、コードはRubyです。

Gemアップデートすればいいじゃん

そうです。Rubyにもslack-ruby-clientというGemがあるので、こちらのバージョンを上げれば対応出来るのですが、私の環境ではGemの依存関係的にslack-ruby-clientのバージョンを上げることができなかったので、自分でなんとかすることにしました。

新しいAPIについて調べた

files.getUploadURLExternalでググれば↓のような記事はたくさん見つかるのですが、いかんせんどれも「URL取得して、URLにファイルをPOSTして、完了するだけ!」としか書かれていません。

言われた通りにやってみた

とりあえず言われた通りにやってみました。

def upload_to_slack(temp_file)
  token = 'Slackのトークン'
  # ステップ1:アップロード先のURLを取得
  params = { token: token, filename: temp_file.original_filename, length: temp_file.tempfile.length }
  res = Net::HTTP.post_form(URI.parse('https://slack.com/api/files.getUploadURLExternal'), params)
  # ステップ2:ファイルをアップロード
  uri = URI.parse(res_json['upload_url'])
  # あれ?どうやってPOSTするんだ?
end

そうです。ステップ1はAPIのマニュアル通りにやればURLが返ってきたのですが、返ってきたURLにどうやってファイルをアップロードするかがどこにも書いていません...
四苦八苦したのですが、公式に聞いたほうが早いと思い問い合わせたところ、

Upload a file by making a POST request to the upload URL.

この操作を実施する際にはパラメータを付ける必要はなく、Body 部分にアップロードしたいファイルをつけてリクエストを実行します(Postman で実行した際のスクリーンショットを添付します)。
その後以下のドキュメントに記載の手順 3 [ Call files.completeUploadExternal to finalize the upload. ] の操作を実施すると指定したチャンネルにファイルがアップロードされるという流れになります。

という、とてもわかりやすい解説がスクショ付きで送られてきました。

「Body部分につけて送る」をヒントに色々調べた結果、下記のサイトを元にアップロードすることには成功しました。

def upload_to_slack(temp_file, channel_id, comment, username)
  token = 'Slackのトークン'
  # メッセージを投稿
  chat_res = SlackClient.chat_postMessage(text: comment, username: username, channel: channel_id)
  # ステップ1:アップロード先のURLを取得
  params = { token: token, filename: temp_file.original_filename, length: temp_file.tempfile.length }
  res = Net::HTTP.post_form(URI.parse('https://slack.com/api/files.getUploadURLExternal'), params)
  # ステップ2:ファイルをアップロード
  uri = URI.parse(res_json['upload_url'])
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  req = Net::HTTP::Post.new(uri.request_uri)
  fd_attr = [[
    'slack_file', Faraday::UploadIO.new(temp_file.tempfile, temp_file.content_type),
    { filename: temp_file.original_filename, content_type: temp_file.content_type }
  ]]
  req.set_form(fd_attr, 'multipart/form-data')
  http.request(req)
  # ステップ3:処理を完了
  params = { token: token, channel_id: channel_id, thread_ts: chat_res.ts, files: [{'id': res_json['file_id']}] }
  Net::HTTP.post_form(URI.parse('https://slack.com/api/files.completeUploadExternal'), params)
end

しかし、これだとファイルのアップロードはエラーにならないのですが、完了の処理が下記のエラーを返すのです。

{
  "ok"=>false, "error"=>"invalid_arguments", 
  "response_metadata"=>{
    "messages"=>["[ERROR] must provide an object [json-pointer:/files/0]"
  }
}

「いやいや。さすがにマニュアル通りに書いてエラーはないでしょ。」と思いつつ、再度お問い合わせしたり色々試したところ、ついに成功しました!

そのコードがこちらです。

正解のコード

def upload_to_slack(temp_file, channel_id, comment, username)
  token = 'Slackのトークン'
  # メッセージを投稿
  chat_res = SlackClient.chat_postMessage(text: comment, username: username, channel: channel_id)
  # ステップ1:アップロード先のURLを取得
  params = { token: token, filename: temp_file.original_filename, length: temp_file.tempfile.length }
  res = Net::HTTP.post_form(URI.parse('https://slack.com/api/files.getUploadURLExternal'), params)
  # ステップ2:ファイルをアップロード
  uri = URI.parse(res_json['upload_url'])
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  req = Net::HTTP::Post.new(uri.request_uri)
  fd_attr = [[
    'slack_file', Faraday::UploadIO.new(temp_file.tempfile, temp_file.content_type),
    { filename: temp_file.original_filename, content_type: temp_file.content_type }
  ]]
  req.set_form(fd_attr, 'multipart/form-data')
  http.request(req)
  # ステップ3:処理を完了
  params = { token: token, channel_id: channel_id, thread_ts: chat_res.ts, files: "[{'id': '#{res_json['file_id']}'}]" }
  Net::HTTP.post_form(URI.parse('https://slack.com/api/files.completeUploadExternal'), params)
end

なんとステップ3で渡すfilesの値がハッシュの配列を文字列化した値でなければいけなかったようです。
マニュアルにも下記のように配列を渡すと明記されていたので、まさかそれを文字列として表現するということになかなか気が付かずかなり苦戦しました。

スクリーンショット 2024-06-07 9.40.52.png

以上、誰かのお役に立てば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?