増田読み/書きスクリプト ver 0.01

途中までできたので、一応、公開。エラー処理はイイカゲンです。
人工無能を走らせるとか、みさくら語に変換するとか…ネタっぽい使い方しか思いつかないな。

↓な感じで使用。

$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