はてなフォトライフAtomAPIで画像を登録してみる
昨日(id:NAT_programming:20070512)の続きで、はてなフォトライフに画像を登録するRubyスクリプトを書いた。
最初は全然うまく登録できなくて、なんでかな〜と思って、はてなキーワード「はてなフォトライフAtomAPI」を含む日記で、同じようなコード書いている人いないかと探してみたら、以下のエントリが参考になった(RubyじゃなくてPython使っているけど・・・)。
PythonでAtomクライアント - Λάδι Βιώσας
このエントリのコードを見ると、HTTPヘッダに"Content-Type: text/xml"を指定しているのに気づく。自分のコードの通信ログを見てみると、http-access2のデフォルトなのか、"content-type: application/x-www-form-urlencoded"が指定されてた。そりゃ、受け取った方が正しくXMLとして扱ってくれないわけだ。
HTTPヘッダを直したら、すんなり画像登録に成功。
せっかくなので、画像登録、フィード参照、画像削除の処理を、以下のようなHatenaPhotoクラスにまとめました。
# # = hatene-phot.rb: Class for Hatena Fotolife AtomAPI # Author: NAT (http://www9.plala.or.jp/NAT/) # require 'digest/sha1' require 'time' require 'base64' require 'http-access2' class HatenaPhoto def initialize(username, password) @username = username @password = password @client = HTTPAccess2::Client.new @client.debug_dev = open("log.txt", "w") #通信ログ出力用 end def get_root @client.get_content("http://f.hatena.ne.jp/atom", nil, get_wsse_header) end def get(id) @client.get_content("http://f.hatena.ne.jp/atom/edit/#{id}", nil, get_wsse_header) end def post(title, file_name) xml = get_post_xml(title, file_name) header = get_wsse_header header["Content-Type"] = "text/xml" response = @client.post("http://f.hatena.ne.jp/atom/post", xml, header) raise "#{response.dump}" if response.status != 201 response.content end def delete(id) response = @client.delete("http://f.hatena.ne.jp/atom/edit/#{id}", get_wsse_header) rause "#{response.dump}" if response.status != 200 end private def get_wsse_header nonce = Digest::SHA1.digest(Digest::SHA1.digest( Time.now.to_s + rand().to_s + Process.pid.to_s)) now = Time.now.iso8601 digest = Digest::SHA1.digest(nonce + now + @password) credentials = "UsernameToken Username=\"#{@username}\", " + "PasswordDigest=\"#{Base64.encode64(digest).chomp}\", " + "Nonce=\"#{Base64.encode64(nonce).chomp}\", " + "Created=\"#{now}\"" { "Accept" => "application/x.atom+xml, application/xml, text/xml, */*", "X-WSSE" => credentials } end def get_post_xml(title, file_name) image = open(file_name, "rb").read %Q(<entry xmlns="http://purl.org/atom/ns#"> <title>#{title}</title> <content mode="base64" type="image/jpeg">#{Base64.encode64(image)}</content> </entry>) # TODO 画像フォーマットが "image/jpeg" 決め打ち end end
ちなみにTODOコメントにも書いているけど、画像フォーマットのタイプが "image/jpeg" 決め打ちなので、JPEG以外の画像を登録するには、ちょっと修正が必要。
このHatenaPhotoクラスの使用例は、下記の通り。
require 'hatena-photo' require 'rexml/document' require 'kconv' require 'open-uri' photo = HatenaPhoto.new("NAT", "XXXXX") # ユーザ名とパスワードを指定する puts "= ルートAtomエンドポイントへのGET(通常はあまり意味なし)" puts photo.get_root.tosjis puts "\n= 画像の登録" response = photo.post("[test]登録テスト", "sample.jpg").tosjis puts response puts "\n= 登録した画像のフィードを参照" entry = REXML::Document.new(response) entry_id = entry.elements['entry/id'].text id = /tag:hatena.ne.jp,.+:fotolife-(.+)/.match(entry_id)[1] puts photo.get(id).tosjis puts "\n= 登録した画像を取得" imageurl = entry.elements['entry/hatena:imageurl'].text open(imageurl) do |f| open(File.basename(imageurl), "wb") do |w| w.write(f.read) end end puts "#{imageurl}" puts " => #{File.basename(imageurl)}" puts "\n登録した画像の削除" photo.delete(id)
HatenaPhotoクラスを使えば、画像ファイルをまとめて登録するRubyコードを書くのも簡単。
例えば、以下のような画像のタイトルと画像ファイル名をカンマ区切りで並べたテキストファイルを用意する。
[花]近所で見かけた桜, RIMG0008.JPG [旅行][駅]鎌倉駅, RIMG0010.JPG [旅行][列車]鎌倉駅でこんな列車が停車してた, RIMG0015.JPG
これを読み込んで、画像をまとめて登録するコードは下記のようになる。
require 'hatena-photo' photo = HatenaPhoto.new('NAT', 'XXXXX') # ユーザ名とパスワードを指定 while line = gets params = line.chomp.split(/\s*,\s*/) next unless params.length == 2 puts params photo.post(*params) end
私は旅行へ行ったあと、画像をまとめて登録することが多いので、結構役に立つかも。