TL;DR
ローカルからサクっとはてなフォトライフへ画像をアップロードするためのrubyスクリプトです。
ユーザ名とAPIキーを使用したWSSE認証方式を用います。
下記コードにて、ENVにユーザ名とAPIキーをセットして、ファイルパスを引数で渡せばアップロード完了です。
https://gist.github.com/chanibarin/4c95dff8858e25cda588115e8c0634ad
はじめに
ローカルからサクっとはてなへ写真をアップロードしたかったのですが、最近のサンプルが全然なく、サクっと動きそうなものが見当たらなかったので調査して作りました。
はてなの資料を愚直に実装するのみです。
http://developer.hatena.ne.jp/ja/documents/fotolife/apis/atom
http://developer.hatena.ne.jp/ja/documents/auth/apis/wsse
WSSE認証
現在(2016/6/9)のはてなのAtomAPIはユーザ名とAPIキーによるWSSE認証もしくはOAuth認証のどちらかでアクセスするのですが、ローカルからサクっとアップロードしたいだけなのでWSSE認証を選択しました。(OAuthの方が最近のサンプルが多そうなので僕自身は結果的にはサクっと終わらなかったという)
WSSE認証はリクエストヘッダにX-WSSEを送ります
X-WSSE: UsernameToken Username="hatena", PasswordDigest="ZCNaK2jrXr4+zsCaYK/YLUxImZU=", Nonce="Uh95NQlviNpJQR1MmML+zq6pFxE=", Created="2005-01-18T03:20:15Z"
これらに入れる値ですが、
Username
はてなID
Nonce
HTTPリクエスト毎に生成したセキュリティ・トークン
Created
Nonceが作成された日時をISO-8601表記で記述したもの
PasswordDigest
Nonce, Created, APIキーを文字列連結しSHA1アルゴリズムでダイジェスト化して生成されたオクテット列を、Base64エンコードした文字列。APIキーは投稿メールアドレスの"@"や"+"以前の文字列になります。はてなブログの管理画面でも確認できます。
ということでこれらをそのまま用意すると良さそうなのですが…ハマりポイントが存在します。
ハマりポイント
NonceとPasswordDigestの一部として入るNonceは同一ではありません…
PasswordDigestに付加するNonceをbase64エンコードしたものがNonceになります。
PasswordDigest = "AAAA"(Nonce) + Created + API_KEY
とすると、
Nonce = Base64.encode64("AAAA")
となります。
APIキーの取得
リンクから飛んでもらえると取得できると思いますが、念のためスクショを貼っておきます。
ブログ設定の詳細設定を選択し、
下の方に行くとAtomPubという項があるので、そこに記載されているAPIキーの値になります。ちなみに、ルートエンドポイントにはてなIDが書かれているので(USERNAME)、こちらからはてなIDも確認できます。
Nonce
ノンスはランダム値を適当に作成して下さい。
base64nonce = SecureRandom.base64(64) # PasswordDigestに使用
nonce = Base64.decode64(base64nonce)
Created
こちらはISO-8601表記で作成してください。
Time.now.utc.iso8601
トークン生成
以上を踏まえて、下記コードにてトークンを生成出来ます。
require 'time'
require 'securerandom'
require 'base64'
timestamp = Time.now.utc.iso8601
base64nonce = SecureRandom.base64(64)
nonce = Base64.decode64(base64nonce)
token = nonce + timestamp + API_KEY
password_digest = Base64.encode64(Digest::SHA1.digest(token)).chomp!
wsse = "UsernameToken Username=\"#{USERNAME}\", PasswordDigest=\"#{password_digest}\", Nonce=\"#{base64nonce}\", Created=\"#{timestamp}\""
アップロード処理
ここまで来たら後は普通にHTTPでポストするだけです。
Sample /9j/2wCEAAQDAwQDAw.../9n/AA== ```POST /atom/post
仕様書には書いてありませんが…、ホストは自アカウントのurlとかではなく、フォトライフのurlになります。
f.hatena.ne.jp
画像ファイルの読み込み
サンプルはゴロゴロ転がってますが一応書いておきます。
require 'mime/types'
require 'base64'
file_path = ARGV[0] # 第一引数を画像ファイルパスとする
content = Base64.encode64(open(file_path).read) # 画像ファイルをロードしてBase64エンコード
mime_type = MIME::Types.type_for(file_path).first # MIMEタイプ取得
title = File.basename(file_path, '.*') # ファイルタイトル取得
アップロード
お待たせしました!アップロードしましょう!
header = {'X-WSSE' => wsse} # ヘッダにWSSEトークンを渡す
entry = <<-"ENTRY" # エントリデータ生成
<entry xmlns="http://purl.org/atom/ns#">
<title>#{title}</title>
<content mode="base64" type="#{mime_type}">
#{content}
</content>
</entry>
ENTRY
client = Net::HTTP.new('f.hatena.ne.jp', 80)
response = client.post('/atom/post', entry, header)
puts response
アップロード確認
#<Net::HTTPCreated:0xXXXXX>
が出力されることを確認したら、フォトライフを見てみましょう。
アップロードされていたら成功です!
お疲れ様でした。
実行コード
こちらから
https://gist.github.com/chanibarin/4c95dff8858e25cda588115e8c0634ad