日本PostgreSQLユーザ会北海道支部 / Ruby札幌合同セミナーで発表しました

しょぼい発表ですみませんでした。あとでコードも置いときます。
(追記2: ニコニコ動画の動画も貼りました(3/7))
D
(追記: コード置きました(3/1))

「日本PostgreSQLユーザ会北海道支部 / Ruby札幌 合同セミナー 2008-02-16」 dara さんの公開マイリスト - ニコニコ動画 に動画も上がる予定です。

発表のときに見せてたコード

スライドの「実際作る」以降で使用したコードです。

tumblr

一応、その前で説明してた

を使用するコードになってます。

  1. 私をフォローしてくれてる人たち(followers)の中に、
  2. 私がフォローしていない人たちがいるので、それをフォローする

プログラムを書きました。
User モデルのテーブルはこんな感じ。名前と URL、follow してるかどうかのフィールドがあります。

class Migration < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string
      t.column :url, :string
      t.column :following, :boolean
    end
  end
  def self.down
    drop_table :users
  end
end

User モデルは下のような感じです。

class User < ActiveRecord::Base
  validates_uniqueness_of :name

  def self.find_by_name_or_create(name)
    self.find_by_name(name) || self.create(:name => name) unless name.empty?
  end

  def before_save
    self.following ||= :false
  end
end

tumblr クラスを作りました。当日の発表では説明してません。#follow あたりはかなり決め打ちです。

class Tumblr
  class LoginError < Exception; end
  class Following < Array
    def initialize(array=[])
      self.replace array
    end
  end

  @@base_url = "http://www.tumblr.com"

  def initialize(user, pass)
    @ua = WWW::Mechanize.new
    env = ENV['http_proxy'] || ENV['HTTP_PROXY']
    if env
      http_proxy = URI(env)
      @ua.set_proxy http_proxy.host, http_proxy.port
    end
    login(user, pass)
  end

  def set_proxy(host, port)
    @ua.set_proxy host, port
  end

  def dashboard(num=0)
    @ua.get("#{@@base_url}/dashboard/#{num}")
  end

  def following
    url = "#{@@base_url}/following"
    doc = Hpricot(@ua.get(url).body)
    t = Tumblr::Following.new(pickup(doc)).each do |user|
      user.following = :true
      user.save
    end
    t
  end

  def followers
    url = "#{@@base_url}/followers"
    doc = Hpricot(@ua.get(url).body)
    t = Tumblr::Following.new(pickup(doc))
  end

  def follow(user=nil)
    if user
      name = user.name
      url = user.url
      page = @ua.get(url)
      f = page.iframes.first
      link = f.src
      page = @ua.get(link)
      f = page.forms.first
      @ua.submit(f)
      puts "Now you follow #{name}: #{url}."
      user.following = :true
      user.save
    end
  end

  def login(user, pass)
    page = @ua.get("#{@@base_url}/login")
    f = page.forms.first
    f.email, f.password = user, pass
    result = @ua.submit f
    raise Tumblr::LoginError, "wrong username or password" if result.forms.size > 0
  end

  private
  def pickup(doc)
    a = []
    (doc/"#following"/"div.username"/"a").each do |element|
      user = User.find_by_name_or_create(element.inner_html)
      user.url = element.get_attribute("href")
      user.save
      a << user
    end
    a
  end
end

実際に動かすコードです。

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'db/development.sqlite3',
  :timeout => 5000
)
user_account = Pit.get("tumblr.com",
                       :require => {
                          :login => 'E-mail address',
                          :password => 'Password'})
t = Tumblr.new(user_account[:login], user_account[:password])
following = t.following
followers = t.followers
followers.each do |follower|
  unless following.include? follower
    puts "#{follower.name} (#{follower.url}) さんをまだフォローしてません。"
    puts "フォローしますか? (y/n)"
    line = gets
    if /\AY|y\Z/ =~ line.chomp
      t.follow follower
    end
  end
end
twitter

twitter の方は twitter4R 使いました、というだけのコードです。あとはこれを RubyCocoa から使う感じです。

class Client
  def initialize
    @twitter = Twitter::Client.new(Pit.get("twitter.com"))
    @last_updated = 10.minutes.ago.utc
  end

  def message(text='')
    puts "posted: #{text}" unless text.empty?
    @twitter.status(:post, text) unless text.empty?
  end

  def timeline_diff
    diff = nil
    begin
      diff = @twitter.timeline_for(:friends, :since => @last_updated)
      @last_updated = Time.now.utc
    rescue Twitter::RESTError => re
      case re.code
      when '304'
        puts re.to_s
      end
      @last_updated = Time.now.utc
    rescue Timeout::Error => re
      pp re
    end
    diff || []
  end
end