トラックバックまわりを実装。
パーサまわりを修正。
# # = masuda.rb # # Copyright (c) 2007 SUGAWARA Genki # # == Example # # require 'masuda' # # diary = Masuda::Diary.new # diary.entries.each {|entry| puts entry.content } # entry = diary.entry('20070712231804') # puts <<EOS # #{entry.title} # #{entry.content} # EOS # entry.trackbacks.each {|trackback| puts trackback.snippet } # # diary.login('my_id', 'my_pass') # diary.my_entries.each {|entry| puts entry.content } # diary.post('Ruby is…', <<EOS) # A dynamic, open source # programming language with a # ... # EOS # require 'base64' require 'cgi' require 'digest/md5' require 'net/http' require 'stringio' require 'time' module Masuda VERSION = '0.3' class Diary @@host = 'anond.hatelabo.jp' @@login_host = 'www.hatelabo.jp' @@user_agent = "RubyMasudaLibrary/#{VERSION}" def initialize @cookies = {} end def proxy(addr, port) end def login(user, pass) res = request('/login', {:mode => 'enter', :key => user, :password => pass, :autologin => 1}, @@login_host) set_cookie(res) if '200' == res.code logined? ? (@user = user) : nil end def logout @cookies.clear end def logined? %w(rk b).all? {|k| @cookies.has_key?(k) and @cookies[k].valid? } end def entry(id) res = request("/#{id}/") return nil unless ('200' == res.code) source = res.body.to_a et= to_entry(source) load_trackbacks(source, self, et) et end def trackbacks(id) entry(id).trackbacks end def entries(page = nil) path = page ? "/?page=#{page}" : '/' res = request(path) ('200' == res.code) ? to_entries(res.body) : nil end def my_entries(page = nil) return nil unless logined? path = page ? "/#{@user}/?page=#{page}" : "/#{@user}/" res = request(path) ('200' == res.code) ? to_entries(res.body) : nil end def post(title, content) return false unless logined? res = request("/#{@user}/edit", {:mode => 'confirm', :rkm => rkm, :id => '', :title => title, :body => content, :edit => "\343\201\223\343\201\256\345\206\205\345\256\271\343\202\222\347\231\273\351\214\262\343\201\231\343\202\213"}) ('302' == res.code) end private def request(path, params = {}, host = @@host) Net::HTTP.version_1_2 Net::HTTP.start(host, 80) {|http| req = Net::HTTP::Post.new(path) req['Host'] = host req['User-Agent'] = @@user_agent req['Cookie'] = @cookies.values.select {|cookie| cookie.apply?(host, path) }.map {|cookie| cookie.header_string }.join('; ') req.body = params.map {|k, v| "#{CGI::escape(k.to_s)}=#{CGI::escape(v.to_s)}" }.join('&') http.request(req) } end def set_cookie(res) if (cookies = res.get_fields('set-cookie')) cookies.each {|raw_cookie| cookie = Cookie.parse(raw_cookie) @cookies[cookie.name] = cookie } end end def rkm return nil unless @cookies.has_key?('rk') rk = @cookies['rk'].value Base64.encode64(Digest::MD5.digest(rk)).strip.sub(/=+\Z/, '') end def to_entry(source, date = '') source = source.to_a if source.kind_of?(String) while line = source.shift if line['<span class="date">'] date.replace(%r|<span class="date">(.+)</span>|.match(line)[1]) elsif line['<div class="section">'] header = source.shift line = source.shift # skip content = StringIO.new until line['<p class="sectionfooter">'] content << line line = source.shift end footer = line id, title = %r|<h3><a href="/([0-9]+)"><span class="sanchor">.+</span></a>(.*)?\Z|.match(header).captures title.gsub!('</h3>', '') time = %r|([0-9]{2}:[0-9]{2})|.match(footer)[1] return Entry.new(self, id, title, content.string, Time.parse("#{date} #{time}")) end end nil end def to_entries(source) source = source.to_a if source.kind_of?(String) ets = [] date = '' until source.empty? et = to_entry(source, date) ets << et if et end ets end def load_trackbacks(source, diary, parent) source = source.to_a if source.kind_of?(String) tbs = [] while (line = source.shift) and not line['</ul>'] if line['<li>'] header = source.shift line = source.shift until source.shift['<div class="box-curve">'] line = source.shift # skip snippet = StringIO.new until line['<span class="curve-bottom">'] snippet << line line = source.shift end id = %r|<a href="http://anond.hatelabo.jp/([0-9]+)"|.match(header)[1] tbs << (tb = Trackback.new(diary, parent, id, snippet.string)) until line['</li>'] load_trackbacks(source, diary, tb) if line['<ul>'] line = source.shift end end end parent.trackbacks = tbs end end # Diary class Entry attr_reader :diary, :id, :title, :content, :time attr_writer :trackbacks def initialize(diary, id, title, content, time) @diary = diary @id = id @title = title @content = content @time = time end def trackbacks @trackbacks or @trackbacks = diary.trackbacks(@id) end end # Entry class Trackback attr_reader :diary, :parent, :id, :snippet attr_accessor :trackbacks def initialize(diary, parent, id, snippet) @diary = diary @parent = parent @id = id @snippet = snippet end def entry @entry or @entry = @diary.entry(@id) end end # Trackback class Cookie attr_reader :name, :value, :expires, :domain, :path, :secure def initialize(source = {}) %w(name value expires domain path secure).each {|k| next unless source.has_key?(k) instance_variable_set("@#{k}", source[k]) } end def valid?(now = Time.now) not @expires or (now <= @expires) end def apply?(domain, path, secure = true, now = Time.now) valid?(now) and (domain.slice(-@domain.length, @domain.length) == @domain) and (path.slice(0, @path.length) == @path) and (not @secure or secure) end def header_string "#{CGI::escape(@name)}=#{CGI::escape(@value)}" end def self.parse(raw_cookie) source = {} raw_cookie.split(/;\s?/).each {|pair| key, value = pair.split('=', 2) next unless key and value key = CGI::unescape(key) value = CGI::unescape(value) case key when 'expires' source['expires'] = Time.parse(value) when 'domain' source['domain'] = value when 'path' source['path'] = value when 'secure' source['secure'] = ('true' == value.downcase) else unless source.has_key?('name') source['name'] = key source['value'] = value end end } Cookie.new(source) end end # Cookie end