はてブのダウンロードをErlangでやってみた

参考にさせていただいたところ
はてなブックマークをダウンロードするスクリプト - 趣味的にっきRubyによるWSSE認証の実装 - 近況はてなフォトライフAtomAPIとは - はてなキーワードConverting Between struct:time and ISO8601 Format - ErlangCentral Wiki
私のコードを見るより、ずっとタメになると思います。
とりあえず、Erlangで困ったのは

  • TIMEZONEが取れない→きめ打ちにした
  • WSSE認証で使うISO 8601形式の日付に変換するライブラリが無い(上記のサイトを参考にして変換したが、日付と時刻のセパレータである"T"とかは自分で付け加えてみた。

一応、wsseとgetatomという2つのモジュールを作って、下記のように実行してみて、自分のはてブの中身をダウンロードできるところまでは来た。
(2009/2/15追記:hatenaのエンドポイントは?date=latestが無い形じゃないと500 Internal Server errorが出るようになっていた。)

> Latest = getatom:new("ユーザ名", "パスワード", "http://b.hatena.ne.jp/atom/feed?date=latest").
[60,63,120,109,108,32,118,101,114,115,105,111,110,61,34,49,
 46,48,34,32,101,110,99,111,100,105,110,103,61|...]
> io:format("~s",[Latest]).


  kgbu\343\201\256\343\203\226\343\203\203\343\202\257\343\203\236\343\203\274\343\202\257
  
  



  
以下略

wsse.erl

 -module(wsse).
 -export([new/2]).

 -define(SHA1DIGESTLENGTH, 20).

new (User, Password) ->
    {A, B, C} = now(),
    random:seed(A, B, C),
    Nonce = nonce(?SHA1DIGESTLENGTH, ""),
    {{Year, Month, Day}, {Hour, Min, Sec}} = Localtime = erlang:localtime(),
    Created =
    io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B",
        [Year, Month, Day, Hour, Min, Sec])  ++
    get_tz(Localtime),

    crypto:start(),
    Digest = binary_to_list(crypto:sha(Nonce ++ Created ++ Password)),

    "UsernameToken Username=\"" ++ User ++ "\", " ++
    "PasswordDigest=\"" ++
    base64:encode_to_string(Digest) ++ "\", " ++
    "Nonce=\"" ++
    base64:encode_to_string(Nonce) ++ "\", " ++
    "Created=\"" ++ Created ++ "\"".

nonce(0,L) -> L ++ [random:uniform(255)];
nonce(N,L) -> nonce(N -1, L ++ [random:uniform(255)]).

%% FIXME : きめ打ちだ!
%%
get_tz(_Localtime) -> "+09:00".

(2009/02/14追記:get_tzは以下のようにも書けたらしい

get_tz(_Localtime) ->
  Datestr = os:cmd("date +%:z"),
  io_lib:format("~6s", [Datestr]).

(2009/02/15追記:ちょっと整理して、英文blogにも投稿してみた)

getatom.erl

 -module(getatom).
 -export([new/3]).

new(User, Password, Uri) ->
    RequestHeader = [{ "X-WSSE" , wsse:new(User, Password)}],

    inets:start(),
    {ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} =
        http:request(get, {Uri, RequestHeader}, [ ], [ ]),
    Body.