ウォンツテック

そでやまのーと

HateFS

ちょっとRubyはてな日記ではてなFileSystemでも作ってみようかと思ったのですが、Ruby(とそのライブラリ)が便利すぎてFSのバイナリをいじるまでに至りませんでした。
はてなのログイン認証にWWW::Mechanize、HTMLの解析にHpricot、ファイルシステム部分にFuseFSを使ってます。とりあえず任意の日付の日記を「diary」ファイルとしてマウントして読むだけのコードを書いてみました。本当はFuseFSを(というかFUSEも)使わずにマウント部分を書いてみたかったんですがRubyであっさり出来すぎたんでやる気失せました。

diaryio.rb

require 'rubygems'
require 'mechanize'
require 'kconv'

class BaseRW
  attr_accessor :date
  def initialize(id, pass, date)
    @id = id
    @pass = pass
    @base_url = "http://d.hatena.ne.jp/" + @id + "/"
    @s_login = "ログイン".toeuc
    @s_move = "こちら".toutf8
    @date = date
    @agent = set_agent()
  end

  private
  def set_agent
    agent = WWW::Mechanize.new
    dialy_page = agent.get(@base_url)
    login_link = dialy_page.links.text(@s_login)
    login_page = agent.get(login_link.href)
    login_form = login_page.forms.first
    login_form['name'] = @id
    login_form['password'] = @pass
    redirect_page = agent.submit(login_form)
    diary_link = redirect_page.links.text(@s_move)
    @base_page = agent.get(diary_link.href)
    return agent
  end

  protected
  def get_day_page
    day_url = @base_url + @date
    day_page = @agent.get(day_url)
    return day_page
  end
end

class ReadDairy < BaseRW
  def initialize(id, pass, date)
    super(id, pass, date)
  end
  def get_page
    day_page = get_day_page()
    day_doc = day_page.root
    page = ""
    (day_doc/"div.body div.section p").each do |body|
      next if /timestamp/ =~ "#{body.inner_html}"
      page += "#{body.inner_html}\n"
    end
    if page == nil
      return nil
    else
      return page.toutf8
    end
  end
end

class WriteDairy < BaseRW
  def initialize(id, pass, date)
    super(id, pass, date)
  end
  def set_page(lines)
    edit_link = @base_page.links.text("日記を書く".toeuc)
    edit_page = @agent.get(edit_link.href)
    edit_form = edit_page.forms.name("edit").first
    edit_form["year"] = @date.slice(0..3)
    edit_form["month"] = @date.slice(4..5)
    edit_form["day"] = @date.slice(6..7)
    edit_form["body"] = lines
    ok_button = edit_form.buttons.name("edit")
    @agent.submit(edit_form, ok_button)
  end
end

はてな日記の読み書き部分。Mechanizeにより認証がものすごく楽です。

argparse.rb

class ArgParse
  def initialize(argv)
    @argv = argv
    if @argv.size < 4 || @argv.size > 6
      puts "usage: ruby hatefs.rb mnt [-r] [-w line] id pass date"
      exit 0
    end
    set_args()
  end

  private
  def set_args
    @mount = @argv[0]
    if /-r/ =~ @argv[1]
      @cmd = "read"
      @id = @argv[2]
      @pass = @argv[3]
      @date = @argv[4]
    elsif /-w/ =~ @argv[1]
      @cmd = "write"
      @lines = @argv[2]
      @id = @argv[3]
      @pass = @argv[4]
      @date = @argv[5]
    else
      @cmd = "read"
      @id = @argv[1]
      @pass = @argv[2]
      @date = @argv[3]
    end
  end
end 

hatefs.rb

require 'rubygems'
require 'mechanize'
require 'fusefs'
require 'kconv'
require 'diaryio'
require 'argparse'

class HateFs
  def initialize(read, write)
    @read = read
    @write = write
  end
  def contents(path)
    ['diary']
  end
  def file?(path)
    path == '/diary'
  end
  def read_file(path)
    @read.get_page
  end
  def write_to(path, str)
    @write.set_page(str)
  end
end

arg = ArgParse.new(ARGV)
read = ReadDairy.new(arg.id, arg.pass, arg.date)
write = WriteDairy.new(arg.id, arg.pass, arg.date)
hatefs = HateFs.new(read, write)
FuseFS.set_root(hatefs)
FuseFS.mount_under ARGV.shift
FuseFS.run

マウント方法

$ ruby hatefs.rb mnt sodex password 20070407

これで以下のように日記がファイルとして参照出来ました。(とりあえず適当に取ってきただけなので汚いですが。。)