Wassr クラス: タイムラインを取得する

対象のユーザや自分のタイムラインを取得するために作った Wassr クラスです。
今のところタイムラインを取得するくらいしかできません。
CodeRepos に上げてみようかな。

require 'rubygems'
require 'mechanize'
require 'hpricot'

# TODO: クラスごとにファイルに分ける gtaka555
# TODO: timeline の取得処理にコードのダブりを調整 gtaka555

class Wassr
  class Message
    attr_accessor :username, :message, :permalink, :date, :favorite

    def initialize
      @favorite = []
    end
  end

  class Favorite
    attr_accessor :username
  end

  class API
  end

  def initialize
    @is_logined = false
  end

  # TODO: 認証処理 gtaka555
  def login(username, passwd)
    @agent = WWW::Mechanize.new

    page = @agent.get('http://wassr.jp/')

    form = page.forms.name('LoginForm').first
    form.login_id = username
    form.login_pw = passwd

    @user_top_page = @agent.submit(form)

    @is_logined = true

    true
  end

  def timeline(limit_date = nil)
    messages = []

    Wassr::walk_timeline_page(@agent, @user_top_page, limit_date)
  end

  # TODO: メソッドの再設計が必要 gtaka555
  def self.public_timeline(username = nil, limit_date = nil)
    return []  if username.nil?

    agent = WWW::Mechanize.new

    # TODO: WWW::Mechanize の exception をとるように gtaka555
    # TODO: close なユーザの場合のエラー処理 gtaka555

    page = agent.get("http://wassr.jp/user/#{username}")

    Wassr::walk_timeline_page(agent, page, limit_date)
  end

  def self.walk_timeline_page(agent, page, limit_date = nil)
    messages = []

    timeline_page = page

    while true
      m = Wassr::parse_html_page(timeline_page.body)

      messages.concat(m)

      # TODO: ページ移動中に発言が流れる可能性があるため、ユニーク処理が必要 gtaka555

      next_link = timeline_page.links.find {|l| l.text =~ /Next/ && l.href =~ %r!^http://wassr.jp/my/\?page=!}
      if next_link.nil?
        break
      end
#     puts "debug: -> #{next_link.href}"

      # TODO: いらない日付の発言は切るように gtaka555
      if !limit_date.nil? && m.last.date <= limit_date
        break
      end

      select(nil, nil, nil, 1.0)  # => sleep 1

      timeline_page = agent.click next_link
    end

    messages
  end

  def self.parse_html_page(html)
    messages = []

    doc = Hpricot(html)

    (doc/"div[@class='OneMsg xfolkentry']").each do |elem|
      msg = Wassr::Message.new

      # TODO: HTML 要素がなかった場合のエラー処理
      msg.username = elem.at("div.messagefoot_container > p.messagefoot > a.MsgUserName").inner_text # username
      msg.permalink = elem.at("div.messagefoot_container > p.messagefoot > a.MsgDateTime")[:href]   # permalink
      msg.message = elem.at("p[@class='message description']").inner_text # message
      date = elem.at("div.messagefoot_container > p.messagefoot > a.MsgDateTime").inner_text # date

      favorite_elems = elem.at("div.favorite_list")
      unless favorite_elems.nil?
        (favorite_elems/"a").each do |favorite_elem|
          favorite = Wassr::Favorite.new
          favorite.username = favorite_elem[:title]

          msg.favorite << favorite
        end
      end

      msg.date = Time.parse(date)
      msg.message = msg.message.gsub(/(^\s+)|(\n$)|(\r\n$)/m, '')

      messages << msg
    end

    messages
  end
end

if $0 == __FILE__
  require 'csv'

  #messages = Wassr::public_timeline('ゆーざめい', Time.parse("2008/09/29 00:00:00"))

  wassr = Wassr.new
  wassr.login('ゆーざめい', 'ぱすわーど')

  messages = wassr.timeline(Time.parse("2008/10/03 00:00:00"))

  CSV.open('timeline.csv', 'w') do |writer|
    messages.each do |message|
      writer << [message.date, message.username, message.message]
    end
  end
end

__END__
#
# 2008/10/03
# * 'Next' リンクの抽出条件を変更
#
# 2008/09/28
# * Wassr::Message に permalink, favorite を追加
# * Wassr::parse_html_page permalink, favorite の解析処理を追加
# * Wassr::Message message の末尾の改行を削除する処理を追加
# * Wassr::walk_timeline_page timeline ページを走査して解析するメソッドを追加
# * Wassr#new 追加
# * Wassr is_logined を追加
#