HaskellでOAuthとTwitter API
ゴールデンウィークの課題としていたHaskellでOAuth。
jsonの解析はまだ行えていませんが、OAuthを使ってjsonを取得するところまでできたので、その事をまとめます。
TwitterでOAuthを使う基本的な流れについてはtwitter developersのauthenticationの項
http://dev.twitter.com/pages/auth
を見てもらうことにして、ここでは各部分について実際のコードを書きます。
標準で入っていないモジュールがありますので、その際にはcabalなどを使っていれてもらえればと思います。
OAuthを使うのに一番の山場はsignatureの生成です。
僕もここでかなり苦戦しました。
Signatureの生成には次のコードを書きました。
Haskellらしくないコードの書き方かもしれませんが、
どのような手順でSignatureを生成しているのかわかるように書いています。
-- Signature.hs -- signatureを生成する関数を提供するモジュールSignature module Signature ( makeSignature, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, URL, Parameter ) where import Data.Word (Word8(..)) import Data.List import Network.HTTP (RequestMethod, urlEncode) -- Base64を使うために次のモジュールをインポートする import qualified Codec.Binary.Base64 as B64 -- HMAC-SHA1を使うために次のモジュールをインポートする import Data.Digest.Pure.SHA (hmacSha1, bytestringDigest, showDigest) import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as L8 type ConsumerKey = String type ConsumerSecret = String type AccessToken = String type AccessTokenSecret = String type URL = String type Parameter = [(String, String)] -- signatureを生成する関数 makeSignature :: URL -> RequestMethod -> ConsumerSecret -> AccessTokenSecret -> Parameter -> String makeSignature url method cSecret aSecret param = str6 where -- keyの作成 key = urlEncode cSecret ++ "&" ++ urlEncode aSecret -- keyを秘密鍵として、signatureBaseStringで作成したsignature base stringのHMAC-SHA1を取得する str4 :: [Word8] str4 = L.unpack $ bytestringDigest $ hmacSha1 (L8.pack key) (L8.pack $ makeSignatureBaseString url method param) -- str4をBase64エンコードする str5 :: String str5 = B64.encode str4 -- str5をURLエンコードする str6 = urlEncode str5 -- signature base stringを生成する関数 makeSignatureBaseString :: URL -> RequestMethod -> Parameter -> String makeSignatureBaseString url method param = str3 where -- メソッド(GET, POST)とURLをエンコードした文字列を"&"で連結する str1 = show method ++ "&" ++ urlEncode url -- paramのキー1=paramの値1&....¶mのキーn=paramの値n -- 【重要】パラメータはソートしておかないといけない str2 = intercalate "&" $ map concatPair (sort param) -- str2をURLエンコードする str2' = urlEncode str2 -- str1とstr2'を"&"で連結する str3 = str1 ++ "&" ++ str2' concatPair :: (String, String) -> String concatPair (x, y) = x ++ "=" ++ y
次にリクエストを作成するコードを書きます。
-- OAuth module module OAuth ( OAuth (..), oauthRequest ) where -- 上で作成したSignatureモジュールのインポート import Signature import Codec.Binary.UTF8.String (encodeString, utf8Encode) import Network.HTTP import Network.URI import Network.HTTP.Proxy (parseProxy) import Network.Browser (browse, request, setProxy, request) import Data.Maybe import Data.List import System.Time (ClockTime(..), getClockTime) import Control.Applicative ((<$>)) import System.Random (randomRIO) -- OAuthデータ型の定義 data OAuth = OAuth { consumerKey :: String, consumerSecret :: String, accessToken :: String, accessTokenSecret :: String } deriving (Show, Eq) -- 乱数の作成 randomInt :: IO Int randomInt = randomRIO (0, maxBound::Int) -- システム時間の取得 getUnixTime :: IO Integer getUnixTime = getUnixTime' <$> getClockTime where getUnixTime' (TOD i _) = i -- oauthを使ってリクエストを作成する関数 oauthRequest :: OAuth -> URL -> RequestMethod -> [(String, String)] -> IO Request_String oauthRequest oauth url method param = do -- 乱数の取得 nonce <- show <$> randomInt -- システム時間の取得 unixTime <- show <$> getUnixTime let -- パラメータをURlエンコードする param' = parameterUrlEncode param -- 次のパラメータもURLエンコードする oauthParam = parameterUrlEncode [("oauth_consumer_key", consumerKey oauth), ("oauth_nonce", nonce), ("oauth_signature_method", "HMAC-SHA1"), ("oauth_timestamp", unixTime), ("oauth_token", accessToken oauth), ("oauth_version", "1.0")] -- Signature.makeSignatureを使ってsignatureを作成する signature = makeSignature url method (consumerSecret oauth) (accessTokenSecret oauth) (param' ++ oauthParam) -- URLに付けるパラメータの作成 urlParam = sort (("oauth_signature", signature): (param' ++ oauthParam)) -- URLの作成 oauthURL = url ++ "?" ++ intercalate "&" (map concatParam urlParam) concatParam :: (String, String) -> String concatParam (x, y) = x ++ "=" ++ y parameterUrlEncode :: [(String, String)] -> [(String, String)] parameterUrlEncode = map $ \(x, y) -> (urlEncode x, urlEncode y) -- リクエストの作成 return $ Request { rqURI = fromJust $ parseURI oauthURL, rqMethod = method, rqHeaders = [], rqBody = "" }
これでリクエストを作成する関数ができたので、実際に使ってみます。
ここでひとつはまったのは、日本語のツイート。
日本語のツイートには、
Codec.Binary.UTF8.String.encodeString
をつかいます。
-- OAuthTwitter.hs module OAuthTwitter where -- 上で作成したモジュールOAuthのインポート import OAuth -- 日本語をツイートするためのUTF8モジュールのインポート import Codec.Binary.UTF8.String (encodeString) import Network.HTTP import Network.HTTP.Proxy (parseProxy) import Network.Browser (browse, request, setProxy, request) import Data.Maybe import Data.List -- user timelineの取得 userTimeline :: OAuth -> [(String, String)] -> IO Request_String userTimeline oauth param = oauthRequest oauth userTimelineURL GET param -- friends timelineの取得 friendsTimeline :: OAuth -> IO Request_String friendsTimeline oauth = do oauthRequest oauth friendsTimelineURL GET [] -- ツイートする update :: OAuth -> String -> IO Request_String update oauth status = oauthRequest oauth updateURL POST [("status", status)] -- リストの取得 listTimeline :: OAuth -> String -> String -> IO Request_String listTimeline oauth userName listName = oauthRequest oauth ("http://api.twitter.com/1/" ++ userName ++ "/lists/" ++ listName ++ "/statuses.json") GET [] -- -- Define URL and oauth for Twitter -- requestTokenURL = "http://api.twitter.com/oauth/request_token" accessTokenURL = "http://api.twitter.com/oauth/access_token" updateURL = "http://api.twitter.com/1/statuses/update.json" userTimelineURL = "http://api.twitter.com/1/statuses/user_timeline.json" friendsTimelineURL = "http://api.twitter.com/1/statuses/friends_timeline.json" cKey = "****" cSecret = "****" aToken = "****" aSecret = "****" oauth = OAuth { consumerKey = cKey, consumerSecret = cSecret, accessToken = aToken, accessTokenSecret = aSecret } main :: IO () main = do -- rq <- userTimeline oauth [("id", "screenName")] -- rq <- friendsTimeline oauth -- 日本語を使うために、encodeStringを使います。 rq <- update oauth $ encodeString "テストツイート" -- rq <- listTimeline oauth "screenName" "listName" (uri, res) <- browse $ do -- プロキシを使うには次をコメントアウトする -- setProxy . fromJust $ parseProxy "proxy.server:8080" request $ rq putStrLn $ rspBody res
参考にしたページは、
【twitter developers Authentication】
http://dev.twitter.com/pages/auth
【EAGLE雑記】
http://d.hatena.ne.jp/eagletmt/20100820/1282253083
です。
あと、OAuthを理解、実装するにあたって、次の本が大変参考になりました。
- 作者: 辻村浩
- 出版社/メーカー: ワークスコーポレーション
- 発売日: 2010/04/21
- メディア: 単行本
- 購入: 4人 クリック: 501回
- この商品を含むブログ (30件) を見る