はてなAPIをRから使う
はてなAPIをRから使ったときのメモです。認証には種類がいくつかありますが、OAuthによる認証を使います。公式ドキュメントはこのへん。
準備
まずは以下のドキュメントに従ってOAuthのアプリケーションを登録します。
「キー」という項目をOAuthトークンの取得に使うのでメモを取っておきましょう。
トークンの取得
はてなAPIではOAuth1.0の認証を使います。httrパッケージでいつもどおりやるだけです。
library(httr) hatena_app <- oauth_app("Hatena", key = "<OAuth Consumer Keyの値>", secret = "<OAuth Consumer Secretの値>") # OAuth1.0なのでrequestも指定 hatena_endpoint <- oauth_endpoint(request = "https://www.hatena.com/oauth/initiate", authorize = "https://www.hatena.ne.jp/oauth/authorize", access = "https://www.hatena.com/oauth/token") hatena_token <- oauth1.0_token(hatena_endpoint, hatena_app)
が、なんか認証がうまくいったと思ったら最後にエラーが出ます…。
#> Error in init_oauth1.0(self$endpoint, self$app, permission = self$params$permission, : #> Bad Request (HTTP 400).
なんだこれは、とおもってdebug()
で調べると、レスポンスに理由が書かれていました。
content(response) #> $oauth_problem #> [1] "parameter_rejected" #> #> $oauth_parameters_rejected #> [1] "oauth_callback"
ドキュメントによると、これは、
parameter_rejected
不要なパラメータが付与されています。oauth_parameters_rejected パラメータに不要なパラメータが「&」区切りでセットされています。
(http://developer.hatena.ne.jp/ja/documents/auth/apis/oauth/problems)
というエラーのようです。はてなのOAuthでは、トークン取得のリクエストにoauth_callback
パラメータが付いているとエラーになるみたいです。ググった感じこれに苦しんでいる人がちらほらいました。
RFC的にはたしかにトークン取得のリクエストにoauth_callback
のパラメータは定義されていません。そういう意味ではhttrの実装が手抜きっぽいんですが、余分なパラメータが付いていた時にどういう扱いをするのが正しいのかはRFCに記述を見つけられませんでした(詳しい方教えてください…)。
ということで誰が悪いのかはわかりませんが、とにもかくにもhttrに手を入れる必要があります。
独自Tokenクラスをつくる
問題はinit_oauth1.0()
という関数が常にoauth_callback
パラメータを付けてしまうことです。このパラメータは関数内で定義されている関数、oauth_sig()
によって組み立てられます。
oauth_sig <- function(url, method, token = NULL, token_secret = NULL, private_key = NULL, ...) { oauth_header(oauth_signature(url, method, app, token, token_secret, private_key, other_params = c(list(...), oauth_callback = oauth_callback()))) }
(httr/oauth-init.R at 1fc659856602f60ff75eb01903513244e3491ec2 · hadley/httr · GitHub)
これはもうお手上げです。oauth_callback
には何かしら値がセットされるので、そのパラメータ自体を外からコントロールすることはできません。あと、scope
というのも渡さないといけないみたいです。
ということで、以下のようにinit_oauth1.0()
自体を置き換える関数を作り、それを使って独自Tokenクラスを作ります。(scope
は外から渡すようにした方がいいけどここでは手を抜きます)
init_oauth1.0_hatena <- function(endpoint, app, permission = NULL, is_interactive = interactive(), private_key = NULL) { oauth_sig <- function(url, method, token = NULL, token_secret = NULL, private_key = NULL, ...) { other_params <- purrr::compact(list(...)) oauth_header(oauth_signature(url, method, app, token, token_secret, private_key, other_params = other_params)) } # 1. Get an unauthorized request token response <- POST(endpoint$request, oauth_sig(endpoint$request, "POST", private_key = private_key, oauth_callback = oauth_callback(), scope = "read_public")) stop_for_status(response) params <- content(response, type = "application/x-www-form-urlencoded") token <- params$oauth_token secret <- params$oauth_token_secret # 2. Authorize the token authorize_url <- modify_url(endpoint$authorize, query = list( oauth_token = token)) verifier <- oauth_listener(authorize_url, is_interactive) verifier <- verifier$oauth_verifier %||% verifier[[1]] # 3. Request access token response <- POST(endpoint$access, oauth_sig(endpoint$access, "POST", token, secret, oauth_verifier = verifier, private_key = private_key), body = "" ) stop_for_status(response) content(response, type = "application/x-www-form-urlencoded") } TokenHatena <- R6::R6Class("TokenHatena", inherit = Token1.0, list( init_credentials = function(force = FALSE) { self$credentials <- init_oauth1.0_hatena( self$endpoint, self$app, permission = self$params$permission, private_key = self$private_key ) } ))
このTokenHatena
を直接new
すればいいのでこれは必要ではないですが、いちおうトークンをつくる関数も定義しておきます。
oauth1.0_token_hatena <- function(endpoint, app, permission = NULL, as_header = TRUE, private_key = NULL, cache = getOption("httr_oauth_cache")) { params <- list(permission = permission, as_header = as_header) TokenHatena$new(app = app, endpoint = endpoint, params = params, private_key = private_key, cache_path = cache) }
これを使ってトークンを取得しましょう。
hatena_token <- oauth1.0_token_hatena(hatena_endpoint, hatena_app)
トークンを使ってリクエストしてみる
トークンを使ってリクエストするにはこんな感じです。
GET("http://n.hatena.com/applications/my.json", config(token = hatena_token))
が、なんかこれ、数回に一度のペースで失敗したりするんですよね…。謎。
GET("http://n.hatena.com/applications/my.json", config(token = hatena_token)) #> Response [http://n.hatena.com/applications/my.json] #> Date: 2017-02-18 05:14 #> Status: 401 #> Content-Type: text/plain; charset=utf-8 #> Size: 31 B GET("http://n.hatena.com/applications/my.json", config(token = hatena_token)) #> Response [http://n.hatena.com/applications/my.json] #> Date: 2017-02-18 05:14 #> Status: 200 #> Content-Type: application/json; charset=utf-8 #> Size: 163 B
シグネチャがおかしいといわれるので、タイムスタンプあたり?とか思うんですけど、今のところは深追いする気力がわかないのでここでギブアップ。
content(x) #> [1] "oauth_problem=signature_invalid"