yunomuのブログ

趣味のこと

HaskellでTwitterにpostする

例によってお酒を飲みながら書いているので文章が冗長です。御託はいいって人は本題まで飛ばしましょう。

枕っていうか前置き

プログラミング言語選択の理由というのは結局のところライブラリだったり環境にインストールされてるのがそれしかなかったりとか、言語自体がどうこうというのとは関係ない部分で決まったりして。
だからメジャーな言語はひと通りやってると意外な時に役に立ったりすることもあります。Perl, PHP, Ruby, Pythonあたりは2つ3つくらい覚えておくとなにかの役に立つかも。いや「何かの役に立つかも」ってレベルですが、少なくとも私は運が良いのか悪いのか、役立つシーンに遭遇したことがある。

で、
私は最近趣味でプログラミングする時はネットワーク環境が無いところでやる事が多いんです。適当にそこら辺のカフェとかで遊んでいるし、モバイルルータとかも持ってないし。
外で開発してて困ることと言ったら、
(1) ライブラリをダウンロードできない
(2) ソースをリポジトリに上げられない
(3) Webで調べ物ができない
(4) ネットワーク系の動作確認ができない
といった感じでしょうか。

(1)は、最初に調べてダウンロードしておけって話です。まあそれでも足りなくて困ったりしますけど、最近はその辺の依存関係を解決してくれるツールとかも多くて良い感じです。
(2)は、gitのローカルでいいような話です。そんなリアルタイムに同期が必要なプロジェクト無いって。趣味ならなおさら。PC壊れたら、残念でした。
(3)は、マニュアルをダウンロードしておけばなんとかなる。公式原理主義万歳。
(4)は、通信と処理をきちんと分離してテストすれば大丈夫なはず!

ってことで最近はもっぱらHaskellを使っています。
いや、使ってみると結構いいですよHaskell。ライブラリも異様に充実してるし、モジュールの粒度が異様に細かいっていうか細かくしないとコンパイル通らないから「通信部分をファイル読み込みに変える」とかも簡単にできるし。それだけで通信に起因する以外のバグは全部除去できる。あとはロジックが間違えていなければね。
今のところだいたい通信系のプログラムでもコンパイルさえ通れば意図通りに動くように作れているので、結構良い感じです。

さらにHaskellはライブラリも異様に充実しているので、下手するとかつてのPerlPHP以上にやりたい放題かもしれません。

そんなわけで今回はTwitterbotを作ってみます。
ここまでが前置きです。ビール1500mlくらいです。

本題

HaskellTwitterAPIを触ろうとしたんですが、なんかあんましいいライブラリが無いっぽいです。直前で「ライブラリが充実してる」とか言っておいてナンですが、でもまあOAuthのライブラリは2つくらいあるみたいですし、HTTPライブラリくらいはさすがにあるだろうよ。というかあります。
ということで、OAuthライブラリを使ってTwitterAPIを触ってみます。

使うライブラリは

authenticate-oauth, http-conduit, conduit, transformers

あたりです。
もうひとつhoauthってちょっと簡単そうなライブラリもあったんですが、何故かビルドできない症状に見舞われたので却下。こっちの方が堅そうだしね。
https://github.com/yesodweb/authenticate
yesodの人が作って今はkonnさんがいじってる。Haskell界隈に詳しい人に言わせるとまあ間違いないでしょうという感じのようで。
ただConduitとかTransformerとか出てきて、最初にモナドがどうとか言ってた時のような嫌な雰囲気が無いではない。

まあでもモナドの時みたいに、やってみれば案外どうってことはないような話なのかもしれません。やってみましょう。

importはだいたいこんな感じ。

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad.IO.Class (liftIO)
import Data.ByteString
import Data.Conduit
import qualified Data.Conduit.Binary as CB
import Network.HTTP.Conduit
import Web.Authenticate.OAuth

Web.Authenticate.OAuthはもちろんですけど、liftIOやNetwork.HTTP.ConduitもHTTPでPOSTするために必要になってくる。他はまあ、パラメータとか表示のためなのであんましどうでもよかったりしますけど。

マニュアルはこれ。
http://hackage.haskell.org/package/authenticate-oauth

これと、あとHTTP.Conduitのマニュアルを眺めてみると、まずHTTPのRequestを作って、それにOAuth用のヘッダとか情報を足していって、最後は普通にGETなりPOSTするような感じみたい。
で、そのOAuth用の情報を付加する関数がsignOAuth。

-- | Add OAuth headers & sign to 'Request'.
signOAuth :: (MonadUnsafeIO m)
          => OAuth              -- ^ OAuth Application
          -> Credential         -- ^ Credential
          -> Request m          -- ^ Original Request
          -> m (Request m)    -- ^ Signed OAuth Request

その前にOAuthとCredentialを作らないといけないらしい。

OAuthはnewOAuth関数で作れるんだけど、これだとOAuthが持ってるURIとかキーとかの情報が空のままだって怒られるので突っ込んであげなきゃいけない。このへんの情報マニュアルに無くてエラーメッセージ出したりソース読んだりしなきゃいけなかったんだけどどういうことなの? Haskell界的には「ソース嫁」でいいの? 個人的にはいいけど。Rubyクラスタ的にはそれがデフォだしな!
……というかこれで正しいの? お行儀的に。(鍵は伏せました)

oauth = newOAuth {
    oauthRequestUri = "https://api.twitter.com/oauth/request_token",
    oauthAccessTokenUri = "https://api.twitter.com/oauth/access_token",
    oauthAuthorizeUri = "https://api.twitter.com/oauth/authorize",
    oauthConsumerKey = consumer_key,
    oauthConsumerSecret = consumer_secret}

CredentialはnewCredential関数で作る。これは見たまんま。やはり鍵は伏せました。

credential = newCredential access_token access_token_secret

RequestはURIをパースして作る。こういう感じ。

main :: IO ()
main = do
    request <- parseUrl "http://twitter.com/statuses/update.json"
    return ()

ただこれだとHTTPメソッドがGETになっちゃうんですけど、Network.HTTP.ConduitのRequestはurlEncodedBodyでBodyパラメータを設定すると、同時にメソッドをPOSTにしてくれるという現実的だけど大胆な仕様がある。
Twitterにpostする時は"status=xxxx"ってやればいいので、ゲイツよろしく第一声で"Hello World!"とか言いたい場合はこうなる。

main :: IO ()
main = do
    request <- parseUrl "http://twitter.com/statuses/update.json"
    let postRequest = urlEncodedBody [("status", "Hello World!")] request
    return ()

これにOAuthヘッダを加えるとこうなる。さっきのoauthとcredentialと合わせて。

main :: IO ()
main = do
    request <- parseUrl "http://twitter.com/statuses/update.json"
    let postRequest = urlEncodedBody [("status", "Hello World!")] request
    signedRequest <- signOAuth oauth credential postRequest
    return ()

あとはこのRequestをPOSTしてやればいいわけなんですが、Requestを投げるためにはHTTP.Conduitのhttp関数を使う必要があって、ホゲーって感じです。

でもまあこの場合はサンプル通りにやればいいので、恐れることはない感じです。
例えばcurl的なサンプル。

main = runResourceT $ do
    manager <- liftIO $ newManager def
    req <- liftIO $ parseUrl "http://www.google.com/"
    res <- http req manager
    responseBody res $$ CB.sinkHandle stdout

これを真似して、

main = runResourceT $ do
    manager <- liftIO $ newManager def
    request <- liftIO $ parseUrl "http://twitter.com/statuses/update.json"
    let postRequest = urlEncodedBody [("status", "Hello World!")] request
    signedRequest <- signOAuth oauth credential postRequest
    response <- http signedRequest manager
    responseBody response $$ CB.sinkHandle stdout

こういう感じ?

私はこれを一行一行書きながら作っていったからいいけど、これの型を後から追いかけようと思うと結構骨が折れますよね。
でもまあこういう感じで一応動くと思います。

というか、こうやってマニュアルだけ見ながらコード書いて、ネット環境あるところっていうか家でテストして一発で動いたので、やっぱりHaskell素敵だと思いました。

最近出たYesod本は付録のConduit解説ばっか読んでます。
Amazon.com: Developing Web Applications with Haskell and Yesod eBook: Michael Snoyman: Kindle Store

2000ml飲み干しました。