twitter gem設定で転ける→opensslかと思いきやSSL証明書のせい

注意:Twitter gemのバージョンが5未満の設定ですので、5以上だと以下の手順の一部(Twitter.configure周辺)は無効ですゴメンナサイ

とても簡単なのだけどメモ。

gemを入れる

$ gem install twitter

だけ。

twitterに登録

Twitter Application Management
からapplicationを登録。

  • Create an application
    • Application Details
      • Name: *
      • Description: *
      • Website: *

の3項目をてきとーに入力。

そうするとDetailタブの画面にConsumer keyとConsumer secretとが表示される。Access tokenとAccess token secretが発行されていないので、一番下のボタンを押して生成。

OAuth settings

Your application's OAuth settings. Keep the "Consumer secret" a secret. This key should never be human-readable in your application.

Access level Read and write
About the application permission model
Consumer key ********
Consumer secret ********
Request token URL https://api.twitter.com/oauth/request_token
Authorize URL https://api.twitter.com/oauth/authorize
Access token URL https://api.twitter.com/oauth/access_token
Callback URL None
Sign in with Twitter Yes
Your access token

Use the access token string as your "oauth_token" and the access token secret as your "oauth_token_secret" to sign requests with your own Twitter account. Do not share your oauth_token_secret with anyone.

Access token ****-*********
Access token secret ********
Access level Read and write

なおOAuthタブのほうがコピペしやすい。
この内容を

Twitter.configure do |config|
  config.consumer_key = YOUR_CONSUMER_KEY
  config.consumer_secret = YOUR_CONSUMER_SECRET
  config.oauth_token = YOUR_OAUTH_TOKEN
  config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
end

Twitter by sferik

に従って設定するだけ。

とても簡単…のはずが

これで一旦はアルファベットのツイートが出来た。
続いて、日本語ツイートを入力しようとしてpryの文字化け、そこでreadlineがOS付属の物であることを思い出した。
ここでMac miniサーバで行ったのと同様にrubyの再インストールを行い、readlineが更新された日本語入力できたわーい☆では再度twitter gemでツイート、としようとしたらツイートできない。
Twitterから発行されたtokenやkeyが悪いのだろうかtwitter gemを入れたときにrbenv rehashしなかったのが悪かったかそうならばrbenv-rehashいれようだけど一度はツイート出来たし…云々と作業しても全くツイートできず。
ここで視点を変えて、Twitter検索を実行させたところ、次のエラーが出た。

/Users/riocampos/.rbenv/versions/1.9.3-p392/lib/ruby/1.9.1/net/http.rb:799:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (Twitter::Error::ClientError)

ざざっと読んでSSLが悪いことはわかる。

Ruby(Net::HTTP?)が SSL証明書を見つけることができなくて、HTTPS 接続に失敗しているのが原因らしい。Net::HTTP が、SSL証明書を見つけられるようにしてあげれば良い。
エラー:OpenSSL::SSL::SSLError SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed - komiyakの通り道

そして何気なくwhich opensslしたところ

$ which openssl
/usr/bin/openssl
$ openssl version
OpenSSL 0.9.8x 10 May 2012
$ brew upgrade openssl
Error: openssl-1.0.1e already installed

…?
homebrewでopensslを入れたのに、なぜそれを使わずにOSのを使ってるの?
ということで一旦削除して再インストール。

$ brew uninstall openssl
Uninstalling /usr/local/Cellar/openssl/1.0.1e...
$ brew install openssl
==> Downloading http://openssl.org/source/openssl-1.0.1e.tar.gz
Already downloaded: /Library/Caches/Homebrew/openssl-1.0.1e.tar.gz
==> perl ./Configure --prefix=/usr/local/Cellar/openssl/1.0.1e --openssldir=/usr
==> make
==> make test
==> make install MANDIR=/usr/local/Cellar/openssl/1.0.1e/share/man MANSUFFIX=ssl
==> Caveats
To install updated CA certs from Mozilla.org:

    brew install curl-ca-bundle

This formula is keg-only: so it was not symlinked into /usr/local.

Mac OS X already provides this software and installing another version in
parallel can cause all kinds of trouble.

The OpenSSL provided by OS X is too old for some software.

Generally there are no consequences of this for you. If you build your
own software and it requires this formula, you'll need to add to your
build variables:

    LDFLAGS:  -L/usr/local/opt/openssl/lib
    CPPFLAGS: -I/usr/local/opt/openssl/include

==> Summary
/usr/local/Cellar/openssl/1.0.1e: 429 files, 16M, built in 6.2 minutes

再確認。

$ which openssl
/usr/bin/openssl

…orz
では--forceオプション付けたlinkを。

$ brew link openssl --force
Linking /usr/local/Cellar/openssl/1.0.1e... 1139 symlinks created
$ which openssl
/usr/local/bin/openssl

よしっ。
これでターミナルを立ち上げ直すと

$ openssl version
OpenSSL 1.0.1e 11 Feb 2013

ようやくhomebrewのopensslを使ってくれました。
ので再度rubyを再インストール(makeに時間が掛かるのに)…。

$ env CONFIGURE_OPTS="--with-readline-dir=`brew --prefix readline` --with-openssl-dir=`brew --prefix openssl`" rbenv install 1.9.3-p392

これで安心。

原因は証明書

しかし残念ながら同じエラーで転けるorz。
ということで本当の原因は別のところにある。

さて、皆さん割と homebrew で openssl 入れて brew link しちゃってる人も多いと思います。そんな環境でその openssl を使って Rubyコンパイルすると、OpenSSL 利用時に証明書エラーが発生します。
curl のサイト上で配布してる cacert.pem は、Mozilla で利用している証明書です。
証明書は curl-ca-bundle という formula で提供されているので、

$ brew install curl-ca-bundle
$ brew list curl-ca-bundle
/usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt
$ cp /usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt /usr/local/etc/openssl/cert.pem

することで、証明書を設置でき、SSL でのエラーは発生しません。
homebrew で入れた openssl を使って Ruby をコンパイルすると SSL 利用時に証明書エラーが発生する場合の対応 - Qiita

追記:

追記:curl-ca-bundleは'14/4/24ごろ正式に廃止されました。
MavericksでRuby1.9.3環境設定 - 別館 子子子子子子(ねこのここねこ)

ですので、以下の作業は好ましくない&上手く行かないと思われます。

そういえば上記のopensslインストールログにも

To install updated CA certs from Mozilla.org:

    brew install curl-ca-bundle

This formula is keg-only: so it was not symlinked into /usr/local.

って出てますよね。なるほどInternetからファイルをダウンロードする場合はcurlを使うのがほとんどだから、curlで使う証明書を更新しなきゃダメなのね。
ということで、これをやってみましょう。

$ brew install curl-ca-bundle
Warning: curl-ca-bundle-1.87 already installed

あ、入ってる。でもkeg-onlyだから手動で移してやるかリンク張るかしないとダメなのね。
コピーじゃなくシンボリックリンクを張っておこう。

$ brew list curl-ca-bundle
/usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt
$ ln -s /usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt /usr/local/etc/openssl/cert.pem

確認。

$ ls -l /usr/local/etc/openssl/
total 32
lrwxr-xr-x  1 riocampos  admin     57  6 15 02:26 cert.pem -> /usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt
drwxr-xr-x  2 riocampos  admin     68  6 14 23:11 certs
drwxr-xr-x  9 riocampos  admin    306  6 15 00:45 misc
-rw-r--r--  1 riocampos  admin  10835  6 15 00:45 openssl.cnf
drwxr-xr-x  2 riocampos  admin     68  6 14 23:11 private

これで上手く行きました☆

NHKラジオの現在放送中の番組名を返すスクリプト

番組表API(非公式)があるのでNokogiriで切って出力形式に。
何度も繰り返しアクセスするのはNHK側に迷惑だろうから、一度取ったデータはtabledataディレクトリにYAMLで保存します。
また番組表は当日午前5時〜翌日午前5時となっているので、午前5時を一日の始まりとして処理しています。
ただし、NHK第2は午前1時前(不定)〜午前6時が放送休止なので、強引に「放送休止中」と出力していますw

勉強中のものなので下手くそですがw

参考:
らじる★らじるの番組表API - 別館 子子子子子子(ねこのここねこ)
スクレイピングのためのNokogiri利用メモ - それはそれ。これはこれ。

# coding: utf-8

require 'open-uri'
require 'nokogiri'
require 'yaml/store'

def read_saved_data(save_file_path)
  saved_data = IO::read(save_file_path)
  readed_data = YAML::load(saved_data)
  readed_data['timetable']
end

def get_timetable(url, save_file_path)
  h = {}
  doc = Nokogiri::HTML(open url)
  items = doc.xpath("//item")
  items.each do |item|
    hj = {}
    item.children.each do |j|
      hj[j.name] = j.text.strip
    end
    index = item.xpath("eventid").text.to_i
    h[index] = hj
  end
  
  # save timetable data as YAML
  metadata = YAML::Store.new(save_file_path)
  metadata.transaction do
    metadata["timetable"] = h
  end
  h
end

def check_nowonair(h, now)
  nowonair = {}
  h.each do |index, value|
    if Time.parse(value["starttime"]) <= now && now <= Time.parse(value["endtime"])
      nowonair[:title] = value["title"]
      nowonair[:starttime] = value["starttime"].scan(/\d\d:\d\d/)[0]
      nowonair[:url] = value["text"]
    end
  end
  nowonair
end

ch = ARGV[0].downcase
case ch
when "r1" then ch_sign = "第1"
when "r2" then ch_sign = "第2"
when "fm" then ch_sign = "FM"
else raise ArgumentError, "argument must be r1/r2/fm"
end

tbl = {} 
now = Time.new
broadcast_time = now - 5 * 60 * 60
save_file_path = File.expand_path("tabledata/nhk#{ch}_#{broadcast_time.strftime("%y%m%d")}_timetable.yaml")

url = "http://cgi4.nhk.or.jp/hensei/api/sche-nr.cgi?tz=all&ch=net#{ch}&date=#{now.strftime("%Y-%m-%d").to_s}"

Dir.mkdir(File.expand_path("tabledata")) unless File.directory?(File.expand_path("tabledata"))

if File.exists?(save_file_path)
  tbl = read_saved_data(save_file_path)
else
  tbl = get_timetable(url, save_file_path)
end

nowonair = check_nowonair(tbl, now)
nowonair[:starttime] = "放送休止中" unless nowonair[:starttime]

puts "NHK#{ch_sign} #{nowonair[:starttime]} #{nowonair[:title]} #{nowonair[:url]}"