はじめに
TumblrではAPIを使って、記事情報と記事に使用する画像を同時にアップロードできます。
しかし、Rubyでそれを行う方法を見つけ出すのに苦労したため、ここに方法を書き残しておきます。
動作確認環境
- Ruby 3.1.2
- faraday 2.7.2
- faraday-multipart 1.0.4
前提条件
OAuth2認証でアクセストークンを取得していること。
https://www.tumblr.com/docs/en/api/v2#oauth2-authorization
使用するAPI
Tumblr APIの/posts
https://www.tumblr.com/docs/en/api/v2#posts---createreblog-a-post-neue-post-format
APIドキュメントによると、こんな感じで記事本文のJSONデータと、添付画像をマルチパート送信する必要があるそうです。
{
"content": [
{
"type": "image",
"media": [
{
"type": "image/jpeg",
"identifier": "some-identifier",
"width": 250,
"height": 200
}
]
}
]
}
--TumblrBoundary
Content-Disposition: form-data; name="json"
Content-Type: application/json
{JSON encoded parameters}
--TumblrBoundary
Content-Disposition: form-data; name="some-identifier"; filename="filename.jpg"
Content-Type: image/jpeg
{image data}
--TumblrBoundary--
コード例
こんな感じで送信できました。
require 'json'
require 'faraday'
require 'faraday/multipart'
access_token = 'your_access_token'
connection = Faraday.new("https://api.tumblr.com") do |conn|
conn.request :authorization, 'Bearer', access_token
conn.request :multipart
end
# JSONのパートを作成
content = {
content: [
{
"type": "image",
"media": [
{
"type": "image/jpeg",
"identifier": "my_image" # この名前は任意
}
]
},
{
type: 'text',
text: 'Hello, world!'
},
]
}.to_json
json_part = Faraday::Multipart::ParamPart.new(content, 'application/json')
# 画像のパートを作成
file_path = '/path/to/my_image.jpg'
file_part = Faraday::Multipart::FilePart.new(
file_path, "image/jpeg", File.basename(file_path))
# "my_image" は content の identifier と一致させる
payload = { json: json_part, my_image: file_part }
# Tumblrに投稿
response = connection.post(
"/v2/blog/your-awesome-blog.tumblr.com/posts", payload)
if response.status.to_i == 201
puts '投稿に成功しました。'
else
puts '投稿に失敗しました。'
puts "エラーコード: #{response.status}"
puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end
備考: Faraday::UploadIO はFaraday 2.0では使えない
ネットを検索すると、「Faradayでマルチパート送信を行うときは Faraday::UploadIO
クラスを使いましょう」という記事がいくつか見つかったのですが、Faraday 2.0では使えなくなっていました。
Faraday 2.0以降では別途 faraday-multipart gem をインストールして、 上のコードのように Faraday::Multipart::FilePart
クラスを使う必要があるようです。
試行錯誤したけどうまくいかなったやり方あれこれ
いろいろ試行錯誤した結果、最終的に上で紹介したコードに落ち着きました。
ここから下は試行錯誤中にうまくいかなかった方法を紹介します。
oauth2 gem単体ではマルチパート送信 できなかった →できた
OAuth2認証をしてAPI連携するときはoauth2 gemが使えます。
画像アップロードを伴わない、テキストだけの記事作成であればoauth2 gemだけで送信できます。
consumer_key = 'your_consumer_key'
consumer_secret = 'your_consumer_secret'
access_token = 'your_access_token'
client = OAuth2::Client.new(
consumer_key, consumer_secret, site: 'https://api.tumblr.com')
access_token_object = OAuth2::AccessToken.new(client, access_token)
params = {
headers: {
'Content-Type' => 'application/json'
},
body: {
content: [
{
type: 'text',
text: 'Hello, world!'
}
]
}.to_json
}
response = access_token_object.post(
"/v2/blog/your-awesome-blog.tumblr.com/posts", params)
if response.status.to_i == 201
puts '投稿に成功しました。'
else
puts '投稿に失敗しました。'
puts "エラーコード: #{response.status}"
puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end
oauth2 gemは内部的にFaradayを使っているので、うまくやればoauth2 gemからマルチパート送信できるのではないか?と思いましたが、結局うまくやる方法が見つからなかったので、直接Faradayを使う方法に切り替えました。
以下のようにすればoauth2 gemからもマルチパート送信できました。
とはいえ、これなら「Faradayを直接使ったコードと大差ないやん」って感じですが……。
consumer_key = 'your_consumer_key'
consumer_secret = 'your_consumer_secret'
access_token = 'your_access_token'
client = OAuth2::Client.new(consumer_key, consumer_secret, site: 'https://api.tumblr.com') do |conn|
conn.request :multipart
end
access_token_object = OAuth2::AccessToken.new(client, access_token)
# JSONのパートを作成
content = {
content: [
{
"type": "image",
"media": [
{
"type": "image/jpeg",
"identifier": "my_image" # この名前は任意
}
]
},
{
type: 'text',
text: 'Hello, world!'
},
]
}.to_json
json_part = Faraday::Multipart::ParamPart.new(content, 'application/json')
# 画像のパートを作成
file_path = '/path/to/my_image.jpg'
file_part = Faraday::Multipart::FilePart.new(
file_path, "image/jpeg", File.basename(file_path))
params = {
body: {
json: json_part,
my_image: file_part
}
}
response = access_token_object.post(
"/v2/blog/your-awesome-blog.tumblr.com/posts", params)
if response.status.to_i == 201
puts '投稿に成功しました。'
else
puts '投稿に失敗しました。'
puts "エラーコード: #{response.status}"
puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end
OAuth1認証もマルチパート送信ができなかった
Tumbler APIのドキュメントではOAuth1認証とOAuth2認証の二通り認証方法が紹介されています。
https://www.tumblr.com/docs/en/api/v2#authentication
OAuth1認証の場合、 oauth gemを使って次のように画像アップロードを伴わない、テキストだけの記事作成は可能です。
require 'oauth'
require 'json'
access_token = 'your_access_token'
access_token_secret = 'your_access_token_secret'
consumer = OAuth::Consumer.new(
consumer_key, consumer_secret, site: 'https://api.tumblr.com')
access_token = OAuth::AccessToken.new(
consumer, access_token, access_token_secret)
params = [
{
content: [
{
type: 'text',
text: 'Hello, world!'
}
]
}.to_json,
{
'Content-Type' => 'application/json'
}
]
response = access_token.post(
"/v2/blog/your-awesome-blog.tumblr.com/posts", *params)
if response.code.to_i == 201
puts '投稿が成功しました。'
else
puts '投稿に失敗しました。'
puts "エラーコード: #{response.code}"
puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end
しかし、oauth2 gemと同様、oauth gemでマルチパート送信する方法はわかりませんでした。
Faradayを使うにしても、Faraday 2.0ではoauth middlewareが使えなくなっており、Faraday 2.0以降ではどのように対応させればよいのかわかりませんでした。
# このコードは昔は動いたっぽいが、Faraday 2.0だと動かない
connection = Faraday.new("https://api.tumblr.com") do |conn|
credentials = {
consumer_key: consumer_key,
consumer_secret: consumer_secret,
token: access_token,
token_secret: access_token_secret
}
conn.request :oauth, credentials
#=> :oauth is not registered on Faraday::Request (Faraday::Error)
まとめ(?)
以上のような経緯から、JSONと画像ファイルのマルチパート送信を行う場合は、FaradayとOAuth2認証を組み合わせるのがベスト(というか、それ以外の方法がわからない)という結論になりました。
Faradayはバージョン2.0(2022年1月リリース)でかなり後方互換性を捨てた変更が入っており、それ以前に書かれたネット記事のコードはそのままでは使えないケースがあるので注意が必要です。
しかも、「その機能はFaraday 2.0では削除されたよ」って言われても、代替方法がよくわからないケースもあるので、「じゃあどうすればいいの!!」と迷子になってしまいがちです。
ちょっとニッチかもしれませんが、今回の僕と似たようなユースケースに遭遇した人はこの記事の内容を参考にしてみてください。