RubyでOAuth

最近、Rubyを弄ってなくすっかり忘れてしまったのでtoRuby勉強会に向けてのリハビリを兼ねて、昔書いたTwitterライブラリをOAuthに対応させてみた。
OAuthは既存のライブラリを使うのではなく id:yoshiori さんのブログ
やる夫と Python で学ぶ Twitter の OAuth - 宇宙行きたい
が分かり易くてとても参考になったのでコードをパクらせて頂いたw

# -*- coding: utf-8 -*-
#
# oauth.rb
#  Twitter用OAuthライブラリ
#
require "openssl"
require "base64"
require "uri"
require "net/http"

class OAuth
  URL = "http://twitter.com"
  REQUEST_TOKEN_PATH = "/oauth/request_token"
  ACCESS_TOKEN_PATH = "/oauth/access_token"
  OAUTH_VERIFIER_PATH = "/oauth/authorize"

  # OAuth spec escape
  def escape(str)
    URI.escape(str, /[^a-zA-Z0-9\-\.\_\~]/)
  end
  private :escape

  # パラメータをエンコードした key=value の形にして separator で繋げる
  def encode_params(hash, separator)
    array = []
    hash.each do |key, value|
      array.push escape(key) + "=" + escape(value)
    end
    array.join(separator)
  end
  private :encode_params

  # キーを元に message で hmac-digest を作成し base64 でエンコード
  def signature(key, message)
    Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, message)).strip
  end
  private :signature

  # nonce word
  def nonce
    OpenSSL::Digest::Digest.hexdigest("MD5", "#{Time.now.to_f}#{rand}")
  end
  private :nonce

  # timestamp
  def timestamp
    Time.now.tv_sec.to_s
  end
  private :timestamp

  # parse
  def parse_body(body)
    hash = {}
    body.split("&").each do |param|
      key, value = param.split("=", 2)
      hash[key] = value
    end
    hash
  end
  private :parse_body

  def request_get(path, header = nil)
    http = Net::HTTP.new(URI.parse(URL).host)
    http.request_get(path, header)
  end
  private :request_get

  def initialize(consumer_key, consumer_secret, oauth_token = nil, oauth_token_secret = nil)
    @consumer_key = consumer_key
    @consumer_secret = consumer_secret

    # 必須パラメータ
    @params = {
      "oauth_consumer_key" => consumer_key,
      "oauth_signature_method" => "HMAC-SHA1",
      "oauth_version" => "1.0",
    }

    if oauth_token != nil && oauth_token_secret != nil
      @params["oauth_token"] = oauth_token
      @oauth_token = oauth_token
      @oauth_token_secret = oauth_token_secret
    end
  end

  # Request Token取得
  def request_token
    params = @params.dup
    params["oauth_timestamp"] = timestamp
    params["oauth_nonce"] = nonce

    # パラメータをソートし,エンコードした key=value の形にして & で繋げる
    params_str = encode_params(params.sort, "&")

    # メソッド, エンコードした URL, 上で作ったパラメータ文字列を & で繋げる
    message = "GET&" + escape(URL + REQUEST_TOKEN_PATH) + "&" + escape(params_str)

    # consumer_secret を元にキーを作成
    key = @consumer_secret + "&"

    # キーを元に message で hmac-digest を作成
    sig = signature(key, message)

    # 作成したダイジェストをパラメータに追加
    params["oauth_signature"] = sig

    # 作成したパラメータを GET のパラメータとして追加
    path = REQUEST_TOKEN_PATH + "?" + encode_params(params, "&")

    res = request_get(path)
    parse_body(res.body)
  end

  # Access Token取得
  def access_token(oauth_token, oauth_token_secret, oauth_verifier)
    params = @params.dup
    params["oauth_token"] = oauth_token
    params["oauth_token_secert"] = oauth_token_secret
    params["oauth_verifier"] = oauth_verifier
    params["oauth_timestamp"] = timestamp
    params["oauth_nonce"] = nonce

    # パラメータをソートし,エンコードした key=value の形にして & で繋げる
    params_str = encode_params(params.sort, "&")

    # メソッド, エンコードした URL, 上で作ったパラメータ文字列を & で繋げる
    message = "GET&" + escape(URL + ACCESS_TOKEN_PATH) + "&" + escape(params_str)

    # consumer_secret を元にキーを作成
    key = @consumer_secret + "&" + oauth_token_secret

    # キーを元に message で hmac-digest を作成
    sig = signature(key, message)

    # 作成したダイジェストをパラメータに追加
    params["oauth_signature"] = sig

    # 作成したパラメータを GET のパラメータとして追加
    path = ACCESS_TOKEN_PATH + "?" + encode_params(params, "&")

    # ヘッダに Authorization:OAuth を追加
    header = {"Authorization" => "OAuth"}

    res = request_get(path, header)
    parse_body(res.body)
  end

  # OAuth Verifier URL
  def oauth_verifier_url(token)
    URL + OAUTH_VERIFIER_PATH + "?oauth_token=" + token["oauth_token"]
  end

  # Authorization Header
  def auth_header(method, url, option = nil)
    params = @params.dup
    params["oauth_timestamp"] = timestamp
    params["oauth_nonce"] = nonce

    # option をパラメータに追加
    opt_key = nil
    if option != nil
      opt_key, value = option.split("=", 2)
      params[opt_key] = value
    end

    # パラメータをソートし,エンコードした key=value の形にして & で繋げる
    params_str = encode_params(params.sort, "&")

    # メソッド, エンコードした URL, 上で作ったパラメータ文字列を & で繋げる
    message = method.to_s.upcase + "&" + escape(url) + "&" + escape(params_str)

    # consumer_secret を元にキーを作成
    key = @consumer_secret + "&" + @oauth_token_secret

    # キーを元に message で hmac-digest を作成
    sig = signature(key, message)

    # 作成したダイジェストをパラメータに追加
    params["oauth_signature"] = sig

    # header に不要なパラメータを削除
    if option != nil
      params.delete(opt_key)
    end

    header = {"Authorization" => "OAuth " + encode_params(params, ",")}
  end
end

OAuthコア以外のソースはこちら
GitHub - m92o/twitter-ruby: Twitter library for Ruby


アクセストークンはこんな風にして取得します。
1. Twitter Application Management でアプリ登録してConsumer Key / Consumer Secretを得る
2. Consumer Key / Consumer Secretを元にOAuth Token / OAuth Token Secret (リクエストークン)及びOAuth Verifier URLを得る

$ ruby requesttoken.rb consumer_key consumer_secret

3. OAuth Verifier URL にブラウザでアクセスしてアプリ使用許可しOAuth Verifierを得る
4. 今まで取得した情報を元にOAuth Token / OAuth Token Secret (アクセストークン)を得る

$ ruby requesttoken.rb consumer_key consumer_secret oauth_token oauth_token_secret oauth_verifier


つぶやくのはこんな感じです。

# -*- coding: utf-8 -*-
require 'twitter'

# 発言内容
text = 'おっぱい'

consumer_key = '最初に使ったやつ'
consumer_secret = '最初に使ったやつ'
oauth_token = 'アクセストークン取得の時に取得した OAuth Token'
oauth_token_secret = 'アクセストークン取得の時に取得した OAuth Token Secret'

ssl = false

tw = Twitter.new(consumer_key, consumer_secret, oauth_token, oauth_token_secret, ssl)
tw.update(text)

ちょっとバグってて発言内容にイコール「=」が入ってると認証エラーになっちゃいます。何故だろう?

ScalaでO'ReillyのiPhoneアプリ版の本からepubを引っこ抜く

404 Blog Not Found:perl - O'ReillyのiPhoneアプリ本からepubをぶっこぬく
これを真似をしてScalaでipa2epubスクリプトを書いてみた。

Rubyで書こうと思ったけど
O'ReillyのiPhone AppからEPUBファイルを抽出するRubyスクリプト - いたさんの日記
すでに書いている人がいたので

Scalaにした

弾さんのと違いはiBooksに自動登録しないのと、zip処理を自前で書いたのでちょっと長い
Scalaコードなので、なんかJavaっぽくなってしまった...
使い方はこんな感じでepubを作ってくれる

$ scala ipa2epub.scala xxx.ipa

Viliv N5でUbuntu

Viliv N5にLubuntu 10.04をインストールしたので設定をメモ
インストール直後は内蔵のOptical Joystick, 無線LANが使えないのでUSB-Mouse, USB-Ethernetを使って設定した。

Optical Joystick

/etc/default/grubGRUB_CMDLINE_LINUXを変更

GRUB_CMDLINE_LINUX="i8042.nomux=1 i8042.noloop=1"

設定を反映

$ sudo update-grub

Video

Intel GMA500(Poulsbo)は標準状態だと使えないのでPPAから入れる。

$ sudo add-apt-repository ppa:gma500/ppa
$ sudo apt-get update
$ sudo apt-get install poulsbo-driver-2d poulsbo-driver-3d poulsbo-config

参考 HardwareSupportComponentsVideoCardsPoulsbo - Ubuntu Wiki

無線LAN

SD-8686用ファームを以下からダウンロード
http://extranet.marvell.com/drivers/driverDisplay.do?driverId=203
アーカイブを解いて所定の場所にコピー

$ unzip SD-8686-LINUX26-SYSKT-9.70.3.p24-26409.P45-GPL.zip
$ tar xvf SD-8686-FEDORA26FC6-SYSKT-9.70.3.p24-26409.P45.tar
$ sudo cp FwImage/sd8686.bin /lib/firmware
$ sudo cp FwImage/helper_sd.bin /lib/firmware/sd8686_helper.bin

GUIのNetworkManagerアプレットを使うと固まるのでCUIで使う設定をする。
/etc/wpa_supplicant/wpa_supplicant.conf (WPA2-PSKの場合)

network={
  ssid="SSIDを記入"
  psk="キーを記入"
  key_mgmt=WPA-PSK
  proto=WPA WPA2
  pairwise=CCMP TKIP
  group=CCMP TKIP
}

/etc/network/interfacesに以下を追加

iface wlan0 inet dhcp
wpa-driver wext
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

無線LANを使う

$ sudo ifup wlan0

無線LANを切る

$ sudo ifdown wlan0

追記 (2010.08.18)
無線LAN使用時にNetworkManagerが動いているとDHCPでアドレスを取得出来ないので事前に次のコマンドで停止させておく。またはパネル上のアイコンを右クリックし「無線を有効にする」を外しても可

$ sudo stop network-manager

Sound

ヘッドフォンだと鳴るが、内蔵スピーカだと音が出ない。
/etc/modprobe.d/alsa-base.conf を弄ってみたが直らない。誰か教えて

Ubuntu 10.04にしたらフォントが汚い

Ubuntu 9.10から10.04にアップデートしてGNOMEパネルとかメニューがビットマップフォントでギザギザして汚くなった場合、次の通り余計な中国語フォントを消すと良いよ

$ sudo apt-get remove ttf-wqy-zenhei

ブックスタンド買ったよ

プログラミングの本を読みながら例題コードを実際に入力するのって、本を押さえながらキーを打たないといけないし、視線移動も大きいのでかなりやり辛い。そこで、このブックスタンドを買ってみた。

EDISON ほんとスタンドS(ほんたった黒セット)

EDISON ほんとスタンドS(ほんたった黒セット)



こんな感じで設置

固定部分

本を置いた時

iPadも置いてみたけど、強度的にはあまり置かない方が良いかも

これでかなりコード入力しやすくなったよ

Dreamweaver CS5でHTML5とiPhone

Adobe Dreamweaver CS5用のHTML5/CSS3エクステンションとCS4/5用iPhoneサイト向けエクステンションが公開されているのでメモ
Adobe - Dreamweaver Support Center : Updaters
http://h2o-space.com/web/product/iphone_dw/