takada-atです。SchemeでTwitterのbotを作成しようと思い、TwitterAPIを取得するスクリプトを作成しました。 と言ってもAPIの種類も多いので、返信の取得やメッセージのポストなど、ごく簡単な部分だけ作ってみます。 Schemeの処理系としては、Gaucheを選びました。 http://practical-scheme.net/gauche/index-j.html HTTPリクエストを発行する部分は以下のようになっています。 認証のためにユーザー名とパスワード情報を Authorizationヘッダにセットします。

  (define (make-auth)
    (string-append "Basic "
                   (base64-encode-string
                    (string-append username ":" password))))
  (define (post message)
  ;;メッセージをポスト
    (http-post
     base-host
     (string-concatenate (list post-path
                         (uri-encode-string message)))
     ""
     :authorization (make-auth)))
ユーザー名とパスワードを指定して接続するために、「クロージャ」を利用した「メッセージパッシング」という技術を利用しています。 参考リンク: http://ja.wikipedia.org/wiki/クロージャ 関数「make-twitter-client」は、ユーザー名とパスワードを引数に与えて呼び出されると、新しい関数「twitter-client」をつくって返します。

(define (make-twitter-client username password)
  ...
  (define (twitter-client order . arg)
    (cond
     ((eq? order 'friends-timeline) (apply friends-timeline arg))
     ((eq? order 'post) (apply post arg))
     ((eq? order 'replies) (apply replies arg))))
twitter-client)
返された関数を、「'post」「'replies」などのメッセージを引数にわたして呼び出すと、twitterにリクエストを投げます。 使い方は以下のようになります。

(define cl (make-twitter-client "hogehoge" "hogepassword")) ;新しく作成した関数を変数 clに代入。
(cl 'replies) ;clにメッセージを渡して、repliesを取得。
「make-twitter-client」呼び出し時に作成された関数「twitter-client」は、定義された時点の環境を記憶しているため、「make-twitter-client」に渡された引数(ユーザー名, パスワード)を覚えています。 この変数スコープの性質を利用することで、ユーザー名やパスワードのような変数を、外からアクセスできないように隠蔽したまま利用することができます。 いわばオブジェクト指向プログラミングでいうプライベート変数のような機能を実現しています。 Gaucheのオブジェクトシステムはプライベートな変数をサポートしていませんが、クロージャを利用することで似た機能を実現できます。

(define-module twitter-client
  (use rfc.http)
  (use rfc.base64)
  (use rfc.uri)
  (use sxml.ssax)
  (use sxml.sxpath)
  (use srfi-13)
  (use srfi-19)
  (export make-status)
  (export make-status-from-result)
  (export make-twitter-client))
(select-module twitter-client)

(define (parse str)
  ;;xmlをsxmlに変換
  (call-with-input-string str
                          (lambda (io) (ssax:xml->sxml io '()))))
(define (make-status sxml)
  ;;statusのsxmlを扱いやすい形式にする
  (define (created_at)
    (string->date (cadr ((car-sxpath '(created_at)) sxml)) "~a ~b ~d ~H:~M:~S ~z ~Y"))
  (define (text)
    (cadr ((car-sxpath '(// text)) sxml)))
  (define (name)
    (cadr ((car-sxpath '(// name)) sxml)))
  (define (id)
    (cadr ((car-sxpath '(// id)) sxml)))
  (define (screen_name)
    (cadr ((car-sxpath '(// screen_name)) sxml)))
  (define (dispatch m . arg)
    ;; 'id, 'text, 'name, 'screen_name, 'created_atでそれぞれの情報を返す
    (cond ((eq? m 'id) (id))
          ((eq? m 'text) (text))
          ((eq? m 'name) (name))
          ((eq? m 'screen_name) (screen_name))
          ((eq? m 'created_at) (created_at))))
dispatch)

(define (make-status-from-result sxml)
  (map make-status ((sxpath '(// status)) sxml))
)

(define (make-twitter-client username password)
  (define base-host "twitter.com")
  (define friends-timeline-path "/statuses/friends_timeline.xml")
  (define post-path "/statuses/update.xml?status=")
  (define replies-path "/statuses/replies.xml")
  (define (make-auth)
    (string-append "Basic "
                   (base64-encode-string
                    (string-append username ":" password))))
  (define (friends-timeline)
    ;;friendのタイムラインを取得
    (parse (values-ref (http-get
     base-host
     friends-timeline
    :authorization (make-auth)) 2))
    )
  (define (replies)
    ;;自分宛ての返信を取得
    (parse (values-ref (http-get
     base-host
     replies-path
    :authorization (make-auth)) 2))
    )
  (define (post message)
    ;;メッセージをポスト
    (http-post
     base-host
     (string-concatenate (list post-path
                         (uri-encode-string message)))
     ""
     :authorization (make-auth)))
  (define (twitter-client order . arg)
    (cond
     ((eq? order 'friends-timeline) (apply friends-timeline arg))
     ((eq? order 'post) (apply post arg))
     ((eq? order 'replies) (apply replies arg))))
twitter-client)


(provide "twitter-client")