途中までできたので、一応、公開。エラー処理はイイカゲンです。
人工無能を走らせるとか、みさくら語に変換するとか…ネタっぽい使い方しか思いつかないな。
↓な感じで使用。
$KCODE = 'u' require 'masuda' d = Masuda::Diary.new d.login('who_am_i', 'hogehoge') p d.post('タイトル', '本文') # 自分のエントリを取得。 d.my_entries.each {|entry| puts entry.title }
本体
もう少しましにする予定。
require 'base64' require 'cgi' require 'digest/md5' require 'net/http' require 'stringio' require 'time' module Masuda VERSION = '0.01' class Diary @@host = 'anond.hatelabo.jp' @@login_host = 'www.hatelabo.jp' @@user_agent = "RubyMasudaLibrary/#{VERSION}" def initialize @cookies = {} 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 entries res = request('/') ('200' == res.code) ? to_entries(res) : nil end def my_entries return nil unless logined? res = request("/#{@user}/") ('200' == res.code) ? to_entries(res) : nil end def post(title, content) return false unless logined? res = request("/#{@user}/edit", {:mode => 'confirm', :rkm => rkm, :id => '', :title => title, :body => content, :edit => 'この内容を登録する'}) ('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_entries(res) date = nil in_the_section = false; header = nil; content = StringIO.new entries = [] res.body.each {|line| unless in_the_section if line['<span class="date">'] date = %r|<span class="date">(.+)</span>|.match(line)[1] elsif line['<div class="section">'] in_the_section = true end next end if line['<h3>'] header = line elsif line['</h3>'] elsif line['<p class="sectionfooter">'] footer = line id, title = %r|<h3><a href="/([0-9]+)"><span class="sanchor">.+</span></a>(.*)\Z|.match(header).captures time = %r|([0-9]{2}:[0-9]{2})|.match(footer)[1] entries << Entry.new(id, title, content.string, Time.parse("#{date} #{time}")) in_the_section = false; header = nil; content = StringIO.new else content << line end } entries end end # Diary class Entry attr_reader :id, :title, :content, :time def initialize(id, title, content, time) @id = id @title = title @content = content @time = time end def trackbacks end end # Entry 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