gem なしの Ruby で Twitter API にアクセスする
こんな野蛮な事をやっているサイトは他にないだろうと思ってPHPのサイトを参考にしながら書いていたのですが,ググったらあった.
gem が導入できない環境で Twitter API にアクセスするようなコードを書きました.
はじめに・前提
こちらのサイトを参考にしています. Twitter APIの細かい説明などはこちらを参照してください.
Rubyのバージョンは2.3.0
です.
プログラム
次の3つのメソッドを作りました.他は補助的なメソッドです.
twitter_oauth_post_requestToken(api_key, api_secret, callback_url)
twitter_oauth_get_accessToken(api_key, api_secret, oauth_token, oauth_token_secret, oauth_verifier)
- 認証情報を基にアクセストークンを取得する.
twitter_statuses_userTimeline(api_key, api_secret, oauth_token, oauth_token_secret, screen_name, count)
screen_name
のタイムラインをcount
個取得する.
コードを示します.
require 'uri' require 'openssl' require 'base64' require 'net/http' require 'json' require 'securerandom' def my_escape(str) URI.escape(str, /[^a-zA-Z0-9._-]/) end def https_post(url, form_data = {}, header = {}) uri = URI.parse(url) https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = true https.verify_mode = OpenSSL::SSL::VERIFY_PEER response = https.start do |https| req = Net::HTTP::Post.new(uri, header) req.set_form_data(form_data) https.request(req) end return response end def https_get(url, form_data = {}, header = {}) uri = URI.parse(url) uri.query = URI.encode_www_form(form_data) https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = true https.verify_mode = OpenSSL::SSL::VERIFY_PEER response = https.start do |https| req = Net::HTTP::Get.new(uri, header) https.request(req) end return response end def twitter_make_signature(request_method, request_url, params, api_secret, api_token_secret) params_str = params.sort.map{|k,v| "#{k}=#{v}" }*'&' signature_key = "#{my_escape(api_secret)}&#{my_escape(api_token_secret)}" signature_data = "#{my_escape(request_method)}&#{my_escape(request_url)}&#{my_escape(params_str)}" hash_raw = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, signature_key, signature_data) return my_escape(Base64.strict_encode64(hash_raw)) end def twitter_oauth_post_requestToken(api_key, api_secret, callback_url) request_url = "https://api.twitter.com/oauth/request_token" time = Time.now() params = { "oauth_callback" => callback_url, "oauth_consumer_key" => api_key, "oauth_nonce" => SecureRandom.uuid, "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => time.to_i.to_s, "oauth_version" => "1.0" } params.each{|k,v| params[k] = my_escape(v.to_s) if k != "oauth_callback" } params["oauth_signature"] = twitter_make_signature("POST", request_url, params, api_secret, "") oauth_header = "OAuth " + params.sort.map{|k,v| "#{k}=#{v}"}*',' response = https_post(request_url, {}, {"Authorization" => oauth_header}) case response when Net::HTTPSuccess results = response.body.split('&').reduce({}){|s,e| k,v=e.split('='); s[k]=v; s } return [response.code, results] else return [response.code, response.body] end end def twitter_oauth_get_accessToken(api_key, api_secret, oauth_token, oauth_token_secret, oauth_verifier) request_url = "https://api.twitter.com/oauth/access_token" time = Time.now() params = { "oauth_consumer_key" => api_key, "oauth_token" => oauth_token, "oauth_verifier" => oauth_verifier, "oauth_nonce" => SecureRandom.uuid, "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => time.to_i.to_s, "oauth_version" => "1.0" } params.each{|k,v| params[k] = my_escape(v.to_s)} params["oauth_signature"] = twitter_make_signature("POST", request_url, params, api_secret, oauth_token_secret) oauth_header = "OAuth " + params.sort.map{|k,v| "#{k}=#{v}"}*',' response = https_post(request_url, {}, {"Authorization" => oauth_header}) case response when Net::HTTPSuccess results = response.body.split('&').reduce({}){|s,e| k,v=e.split('='); s[k]=v; s } return [response.code, results] else return [response.code, response.body] end end def twitter_statuses_userTimeline(api_key, api_secret, oauth_token, oauth_token_secret, screen_name, count) request_url = "https://api.twitter.com/1.1/statuses/user_timeline.json" params_option = { "screen_name" => screen_name, "count" => count } time = Time.now() params = { "screen_name" => screen_name, "count" => count, "oauth_consumer_key" => api_key, "oauth_token" => oauth_token, "oauth_nonce" => SecureRandom.uuid, "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => time.to_i.to_s, "oauth_version" => "1.0" } params.each{|k,v| params[k] = my_escape(v.to_s)} params["oauth_signature"] = twitter_make_signature("GET", request_url, params, api_secret, oauth_token_secret) oauth_header = "OAuth " + params.sort.map{|k,v| "#{k}=#{v}"}*',' response = https_get(request_url, params_option, {"Authorization" => oauth_header}) case response when Net::HTTPSuccess results = JSON.parse(response.body) return [response.code, results] else return [response.code, response.body] end end
長いね.メソッドだけなので,このコードだけでは動きません.
嵌ったところ
URI.escape
デフォルトの URI.escape
はPHPの rawurlencode
とは異なるので,URI.escape(str, /[^a-zA-Z0-9._-]/)
とする必要がある.
滅茶苦茶嵌った.
oauth_callback はエスケープしない
request_token する時は callback url をパラメータに含める必要があるが,上で挙げた参考サイトでも触れられている通り,エスケープしない状態でパラメータにセットして signature を作る必要がある.
Base64.strict_encode64
Base64.encode64
ではなく,Base64.strict_encode64
を使う.
メソッド使用例
認証からタイムライン読み出しの流れのコードを載せます.xreaサーバで動作確認しました.
#!/usr/local/bin/ruby require 'cgi' require 'cgi/session' require 'uri' require 'openssl' require 'base64' require 'net/http' require 'securerandom' require 'json' # ========== # ここに上のメソッド群が入る # ========= @cgi = CGI.new @session = CGI::Session.new(@cgi) begin # アプリケーションの情報をここに入れる @api_key, @api_secret, @callback = ["APIKEY", "APISECRET", "http://example.com/callback_url"] case @cgi.request_method when 'GET' oauth_token = @cgi['oauth_token'] oauth_verifier = @cgi['oauth_verifier'] if !oauth_token.empty? && !oauth_verifier.empty? # twitter認証ページから戻ってきた oauth_token_secret = @session["oauth_token_secret"] result_code, result_param = twitter_oauth_get_accessToken(@api_key, @api_secret, oauth_token, oauth_token_secret, oauth_verifier) if result_code == "200" oauth_token = result_param["oauth_token"] oauth_token_secret = result_param["oauth_token_secret"] screen_name = result_param["screen_name"] tl_c, tl_result = twitter_statuses_userTimeline(@api_key, @api_secret, oauth_token, oauth_token_secret, screen_name, 10) if tl_c == "200" print @cgi.header({"status" => "OK",'type' => 'text/plain'}) puts "accept!!" p [oauth_token, oauth_verifier] p result_param p tl_c puts JSON.pretty_generate(tl_result) exit else print @cgi.header({"status" => "OK",'type' => 'text/plain'}) puts "failure: statuses_userTimeline" p [oauth_token, oauth_verifier] p result_param p tl_c,tl_result exit end else print @cgi.header({"status" => result_code.to_i, 'type' => 'text/plain'}) puts "failure: get_accessToken" p [oauth_token, oauth_verifier] p result_param exit end end if !@cgi["denied"].empty? # twitter認証ページをキャンセルした print @cgi.header({"status" => result_code.to_i, 'type' => 'text/plain'}) puts "denied" exit end result_code, result_param = twitter_oauth_post_requestToken(@api_key, @api_secret, @callback) if result_code == "200" oauth_token = result_param["oauth_token"] oauth_token_secret = result_param["oauth_token_secret"] @session['oauth_token_secret'] = oauth_token_secret print @cgi.header({"status" => "REDIRECT", "Location" => "https://api.twitter.com/oauth/authorize?oauth_token=#{oauth_token}"}) exit else print @cgi.header({"status" => result_code.to_i, 'type' => 'text/plain'}) puts "failure: post_requestToken" p [result_code, result_param] exit end else print @cgi.header({"status" => "NOT_IMPLEMENTED", 'type' => 'text/plain'}) puts "nop" exit end rescue print @cgi.header({"status" => "OK", 'type' => 'text/plain'}) puts "error!" tr=$@*"\n" puts "#{tr}\n#{$!}" end
2017/09/21: 本質とは異なる部分の修正