はてなグラフにデータをポストするスクリプト,あるいは 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

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