GaucheでOAuthを使ってTwitterに投稿する

自分がGaucheでOAuthを使うために試行錯誤していたら、
id:SaitoAtsushiさんがGaucheでOAuthを使うコードを書いてくれていたようなので(http://d.hatena.ne.jp/SaitoAtsushi/20100429/1272545442)、
そのコードを元に、Twitterでつぶやくコードを作ってみました。

;APIのURLをapi.twitter.comにしてみた
;uri-encode-stringを日本語に対応させてみた
(use rfc.http)
(use rfc.sha)
(use rfc.hmac)
(use rfc.base64)
(use www.cgi)
(use math.mt-random)
(use gauche.uvector)

(define consumer-key "Consumer Key")
(define consumer-secret "Consumer Secret")

(define message "GaucheでOAuthを使ってつぶやいてみる")

(define (uri-encode-string str)
  (call-with-string-io str
    (lambda(in out)
      (while (read-byte in) (compose not eof-object?) => ch
             (if (char-set-contains? #[a-zA-Z0-9.~_-] (integer->char ch))
                 (write-char (integer->char ch) out)
                 (format out "%~2,'0X" (char->integer (integer->char ch))))))))

(define (time-stamp)
  (number->string (sys-time)))

(define (random-string)
  (let ((random-source
         (make <mersenne-twister> :seed (sys-time)))
        (v (make-u32vector 10)))
    (mt-random-fill-u32vector! random-source v)
    (digest-hexify (sha1-digest-string (x->string v)))))

(define (query-compose query)
  (string-join (map (cut string-join <> "=") query) "&"))

(define (signature method uri info :optional (token-secret ""))
  (let* ((query-string (query-compose info))
         (signature-basic-string
          (string-append method "&"
                         (uri-encode-string uri) "&"
                         (uri-encode-string query-string))))
    (uri-encode-string
     (base64-encode-string
      (hmac-digest-string signature-basic-string
                          :key #`",|consumer-secret|&,|token-secret|"
                          :hasher <sha1>)))))

; Request Tokenの取得
(define query
  `(("oauth_consumer_key" ,consumer-key)
    ("oauth_nonce" ,(random-string))
    ("oauth_signature_method" "HMAC-SHA1")
    ("oauth_timestamp" ,(time-stamp))
    ("oauth_version" "1.0")))

(define s (signature "POST" "http://api.twitter.com/oauth/request_token" query))
(define token
  (receive (status header body)
      (http-post "api.twitter.com"
                 "/oauth/request_token"
                 (query-compose
                  `(,@query ("oauth_signature" ,s))))
    (cgi-parse-parameters :query-string body)))

(define request-token (cadr (assoc "oauth_token" token)))
(define request-token-secret (cadr (assoc "oauth_token_secret" token)))

; OAuth Verifierの取得
(display "open this url.")
(newline)
(format #t "https://api.twitter.com/oauth/authorize?oauth_token=~A" request-token)
(newline)
(newline)
(display "input pin: ")
(flush) ;これが無いと、「input pin: 」と表示されるのがread-lineの後になってしまう。
(define oauth-verifier (read-line))

; Access Tokenの取得
(define query
  `(("oauth_consumer_key" ,consumer-key)
    ("oauth_nonce" ,(random-string))
    ("oauth_signature_method" "HMAC-SHA1")
    ("oauth_timestamp" ,(time-stamp))
    ("oauth_token" ,request-token)
    ("oauth_verifier" ,oauth-verifier)))

(define s (signature "GET"
                      "http://api.twitter.com/oauth/access_token"
                      query
                      request-token-secret))
(define token
  (receive (status header body)
      (http-post "api.twitter.com"
                 "/oauth/access_token"
                 (query-compose
                  `(,@query ("oauth_signature" ,s))))
    (cgi-parse-parameters :query-string body)))

(define access-token (cadr (assoc "oauth_token" token)))
(define access-token-secret (cadr (assoc "oauth_token_secret" token)))

; つぶやく
(define query
  `(("oauth_consumer_key" ,consumer-key)
    ("oauth_nonce" ,(random-string))
    ("oauth_signature_method" "HMAC-SHA1")
    ("oauth_timestamp" ,(time-stamp))
    ("oauth_token" ,access-token)))

(define s (signature "POST"
                      "http://api.twitter.com/statuses/update.json"
                      `(,@query ,`("status" ,(uri-encode-string message))) access-token-secret))
(http-post "api.twitter.com"
           "/statuses/update.json"
           (format "status=~A" (uri-encode-string message))
           :Authorization (format "OAuth ~A"
                                  (string-join (map (cut string-join <> "=") `(,@query ("oauth_signature" ,s))) ", ")))

こんなコードです。

変更点

元のコードはRequest Tokenを取得するところまでなので、OAuth Verifier、Access Token、Twitter投稿の部分を書きました。

OAuthは、URLエンコードの結果の十六進数は、アルファベットを大文字にする必要があるので、
uri-encode-stringはSaitoAtushiさんが作ったものを使っていますが、元のコードでは日本語に対応していないので、
「一文字ずつ読み込む」ところを「一バイトずつ読み込む」ようにして日本語に対応しました。

Twitter APIのURLも、「twitter.com」ではなく「api.twitter.com」にしています。

実行する方法

「Consumer Key」「Consumer Secret」は、自分の使うものに変更してください。
「"GaucheでOAuthを使ってつぶやいてみる"」の部分は、Twitterに投稿したい文字列に変更してください。

コードをファイル(ここでは「oauth.scm」という名前)に保存して、

gosh oauth.scm

のようにすれば実行できます。
実行してしばらくすると、「open this url.」の後にURLが出るので、そのURLにアクセスして、「許可する」を押して、
出てくる数字を、「input pin:」と出てきたところに入力してエンターを押してください。
しばらく待つと、Twitterにメッセージが投稿されます。