はてなグラフにデータをポストするスクリプト,あるいは hatenaapigraph の gem がバグってる件

はてなグラフを試してみた。で,いちいちログインしてデータを入力するのも面倒なので,スクリプトを書こうと思ってヘルプを見たら,はてなグラフ数値登録API の gem があるじゃないか。すばらしい。
早速インストールしてスクリプトを書いた(というかサンプルそのまんま)。

 #! ruby -Ku
 
 require 'rubygems'
 require 'hatena/api/graph'
 
 graph = Hatena::API::Graph.new('takatoh', 'xxxxxxxx')
 graph.post('読んだページ数', Time.now, 20)

実行してみるとエラーが……

 ^o^ >hgpost.rb
 C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `initialize': getaddrinfo: no addres
 s associated with hostname. (SocketError)
         from C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `open'
         from C:/usr/ruby/lib/ruby/1.8/net/http.rb:560:in `connect'
         from C:/usr/ruby/lib/ruby/1.8/timeout.rb:48:in `timeout'

 (以下略)

えーと,これはあれだ,たぶんプロキシのせいだ。ライブラリのファイル(lib/hatena/api/graph.rb)をのぞいてみると案の定プロキシには対応してないっぽい。とりあえず,べたにプロキシのホスト名とポートを書き足してやるとエラーも出ずちゃんと動いた。


ところが,ブラウザでグラフのページにアクセスしてみると,「読んだページ数」のグラフにデータをポストしたはずなのにそれは更新されず,代わりに「E8AAADE38293E381A0E3839A」という新しいグラフができていた。
これはどういう訳か。ヘルプには

指定されたグラフ名に該当するグラフが存在しない場合はグラフ作成を行った後データ追加、存在する場合は該当日付のデータ上書きを行います。

とある。試しに「TestGraph」というグラフ名でポストしてみるとちゃんと「TestGraph」グラフができていた。どうやら日本語のグラフ名がいけないらしい。文字コードUTF-8 にしてあるのに。


再度ライブラリをのぞいてみる。グラフ名をリクエストのパラメータに指定するときにエンコードしている(ファイルの29行目,下のコードの2行目)。

         params = {
           :graphname => URI::encode(graphname),
           :date => date,
           :value => value,
         }

もしかしてここか?思い切ってエンコードするのをやめてみたら,おお,うまくいった。


結局,ライブラリファイルを直接書き変えるのはやめて,問題のメソッドを再定義することにした。プロキシは環境変数 http_proxy を参照する。ポストするデータはコマンドラインから指定。ユーザIDやら何やらが直に書いてあるのは単なる手抜き。

 #! ruby -Ku
 
 require 'rubygems'
 require 'hatena/api/graph'
 
 module Hatena
   module API
     class Graph
       def post(graphname, date, value)
         value = value.to_f
         date = date.strftime DATE_FORMAT
         headers = {
           'Access' => 'application/x.atom+xml, application/xml, text/xml, */*',
           'X-WSSE' => wsse(@username, @password),
         }
         params = {
           :graphname => graphname,
           :date => date,
           :value => value,
         }
         res = http_post GRAPH_API_URI, params, headers
         raise GraphError.new("request not successed: #{res}") if res.code != '201'
         res
       end
 
       private 
       def http_post(url, params, headers)
         req = ::Net::HTTP::Post.new(url.path, headers)
         req.form_data = params
         req.basic_auth url.user, url.password if url.user
         proxy_host = nil
         proxy_port = nil
         if proxy = ENV['http_proxy']
           proxy = URI.parse(proxy)
           proxy_host = proxy.host
           proxy_port = proxy.port
         end
         ::Net::HTTP.new(url.host, url.port, proxy_host, proxy_port).start {|http| http.request(req) }
       end
     end
   end
 end
 
 graph = Hatena::API::Graph.new('takatoh', 'xxxxxxxx')
 graph.post('読んだページ数', Time.now, ARGV.shift.to_i)


と,ここまで書いてから気がついたけど,グラフ名はリクエストのパラメータで指定するんだからエンコードするのが正しいんだよな。てことはサービスの側のバグか?



追記:
調べてみたけど,やっぱり hatenaapigraph のバグのようだ。
リクエストオブジェクト(ここでは Net::HTTP::Post)にフォームデータをセットするのは Net::HTTPHeader モジュール(Net::HTTP::Post はこのモジュールを include している)の set_form_data メソッドで,このメソッドは受け取ったデータをエンコードする。だから Hatena::API::Graph の中ではエンコードする必要がない。

     # Set header fields and a body from HTML form data.
     # +params+ should be a Hash containing HTML form data.
     # Optional argument +sep+ means data record separator.
     #
     # This method also set Content-Type: header field to
     # application/x-www-form-urlencoded.
     def set_form_data(params, sep = '&')
       self.body = params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}"  }.join(sep)
       self.content_type = 'application/x-www-form-urlencoded'
     end
 
     alias form_data= set_form_data

さて,これは何処に報告すればいいのかな。