Twitter.scm の手直し

  • 関数を生成する関数を書いた
  • TwitterAPI のオプション引数に対応
;; 誰かの friends 一覧。
;; 認証は要らないのでアカウント情報のかわりに #f を渡す。
(for-each (lambda (x)
            (format #t "~a: ~a~%" (ref* x 'user 'name) (ref x 'text)))
          (twitter-friends-timeline #f :id "masa_edw"))

;; アカウント情報
(define myself (make <twitter-account> :username-or-email "hogehoge" :password "brabra"))

;; 自分の friends 一覧
(for-each (lambda (x)
            (format #t "~a: ~a~%" (ref* x 'user 'name) (ref x 'text)))
          (twitter-friends-timeline myself))

;; ステータス更新
(twitter-update myself "using twitter.scm")
;; -*- coding: utf-8 -*-
;;
;; Twitter.scm
;;
;; see:
;; http://groups.google.com/group/twitter-development-talk/web/api-documentation
;;

(use rfc.http)
(use rfc.base64)
(use rfc.uri)
(use sxml.ssax)
(use util.match)

(define *user-agent* "Twitter.scm")

(define-class <twitter-account> ()
  ((password :init-keyword :password)
   (username-or-email :init-keyword :username-or-email)))

(define-class <twitter-status> ()
  ((created-at :init-keyword :created-at)
   (id :init-keyword :id)
   (text :init-keyword :text)
   (user :init-keyword :user)))

(define-class <twitter-user> ()
  ((id :init-keyword :id)
   (name :init-keyword :name)
   (screen-name :init-keyword :screen-name)
   (location :init-keyword :location)
   (description :init-keyword :description)
   (profile-image-url :init-keyword :profile-image-url)
   (url :init-keyword :url)
   (protected :init-keyword :protected)
   (status :init-keyword :status)
   (profile-background-color :init-keyword :profile-background-color)
   (profile-text-color :init-keyword :profile-text-color)
   (profile-link-color :init-keyword :profile-link-color)
   (profile-sidebar-fill-color :init-keyword :profile-sidebar-fill-color)
   (profile-sidebar-border-color :init-keyword :profile-sidebar-border-color)
   (friends-count :init-keyword :friends-count)
   (followers-count :init-keyword :followers-count)
   (favourites-count :init-keyword :favourites-count)
   (statuses-count :init-keyword :statuses-count)))

(define (twitter-authorization-string twitter)
  (let ((password (slot-ref twitter 'password))
        (username-or-email (slot-ref twitter 'username-or-email)))
    (let1 base64str (base64-encode-string #`",|username-or-email|:,|password|")
      #`"Basic ,|base64str|")))

;; utility
(define (car1 pair)
  (if (null? pair)
    pair
    (car pair)))

(define (tagname->slotname tag)
  (string->symbol (regexp-replace-all #/_/ (symbol->string tag) "-")))

(define (twitter-parse-user-status class inner inner-parser msg)
  (lambda (sxml)
    (let1 instance (make class)
      (for-each (match-lambda
                  (((? (lambda (tag) (eq? tag inner)) slot) value ...)
                   (slot-set! instance slot (inner-parser value)))
                  (((? (lambda (tag) (slot-exists? instance (tagname->slotname tag)))
                       tag)
                    value)
                   (slot-set! instance (tagname->slotname tag) value))
                  ((tag)		; do nothing
                   ())
                  (i
                   (error (format #f "No such slot in ~a:" class) (tagname->slotname (car i)))))
                sxml)
      instance)))

(define twitter-parse-user
  (twitter-parse-user-status <twitter-user> 'status (cut twitter-parse-status <>) "user"))

(define twitter-parse-status
  (twitter-parse-user-status <twitter-status> 'user (cut twitter-parse-user <>) "status"))

(define (twitter-parse sxml)
  (match sxml
    ((or ('*TOP* x ...)
         (('*PI* _ ...) x))
     (twitter-parse x))
    (('statuses ('status x ...) ...)
     (map twitter-parse-status x))
    (('users ('user x ...) ...)
     (map twitter-parse-user x))
    (('status x ...)
     (twitter-parse-status x))
    (('user x ...)
     (twitter-parse-user x))
    (('nil-classes)
     ())))

(define (twitter-parse-xml xml)
  (let1 sxml (ssax:xml->sxml (open-input-string xml) ())
    (twitter-parse sxml)))

(define (twitter-get url . maybe-twitter)
  (let ((twitter (get-optional maybe-twitter #f)))
    (receive (status header body)
        (if twitter
          (http-get "twitter.com" url
                    :user-agent *user-agent*
                    :authorization (twitter-authorization-string twitter))
          (http-get "twitter.com" url
                    :user-agent *user-agent*))
      (if (string=? status "200")
        (twitter-parse-xml body)
        status))))

(define (twitter-post url twitter data)
  (receive (status header body)
      (http-post "twitter.com" url data
                 :user-agent *user-agent*
                 :authorization (twitter-authorization-string twitter))
    (if (string=? status "200")
      (twitter-parse-xml body)
      status)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;
;; Status Methods
;;

;; utility
(define (alist->query alist)
  (if (null? alist)
    ""
    (string-append "?"
		   (string-join (map (lambda (x)
				       (format #f "~a=~a"
					       (uri-encode-string (x->string (car x)))
					       (uri-encode-string (x->string (cadr x)))))
				     alist)
				"&"))))

(define (twitter-public-timeline . args)
  (let-keywords args ((id #f))
    (twitter-get (if id
                   #`"/statuses/public_timeline.xml?since_id=,(uri-encode-string id)"
                   "/statuses/public_timeline.xml"))))

(define (twitter-friends-timeline twitter . args)
  (let-keywords args ((id #f)
                      (since #f))
    (let ((xmlstr (if id
		    #`"friends_timeline/,(uri-encode-string id).xml"
		    "friends_timeline.xml"))
	  (sincestr (if since #`"?since=,(uri-encode-string since)" "")))
      (twitter-get (string-append "/statuses/" xmlstr sincestr) twitter))))

(define (twitter-user-timeline twitter . args)
  (let-keywords args ((id #f)
                      (count #f)
                      (since #f))
    (let ((xmlstr (if id
		    #`"/statuses/user_timeline/,(uri-encode-string id).xml"
		    #`"/statuses/user_timeline.xml"))
	  (query (alist->query (filter cadr `((count ,count) (since ,since))))))
      (twitter-get (string-append xmlstr query) twitter))))

(define (twitter-show twitter id-number)
  (twitter-get #`"/statuses/show/,(uri-encode-string id-number).xml" twitter))

(define (twitter-update twitter status)
  (if (> (string-length status) 140)
    (error "Too long status: " status)
    (twitter-post "/statuses/update.xml" twitter
                  #`"status=,(uri-encode-string status)")))

;;
;; User Methods
;;

(define (twitter-friends twitter . args)
  (let-keywords args ((id #f))
    (if id
      (twitter-get #`"/statuses/friends/,(uri-encode-string id).xml")
      (twitter-get "/statuses/friends.xml" twitter))))

(define (twitter-followers twitter)
  (twitter-get "/statuses/followers.xml" twitter))

(define (twitter-featured twitter)
  (twitter-get "/statuses/featured.xml" twitter))

(define (twitter-user-show twitter id)
  (twitter-get #`"/users/show/,(uri-encode-string id).xml" twitter))

;;
;; Direct Message Methods
;;

(define (twitter-direct-messages twitter . args)
  (let-keywords args ((since #f))
    (if since
      (twitter-get #`"/direct_messages.xml?since=,(uri-encode-string since)" twitter)
      (twitter-get "/direct_messages.xml" twitter))))

(define (twitter-direct-message-new twitter user text)
  (if (> (string-length text) 140)
    (error "Too long text: " text)
    (twitter-post "/direct_messages/new.xml" twitter
                  #`"user=,(uri-encode-string user)&text=,(uri-encode-string text)")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (easy-output list)
  (if (pair? list)
    (for-each (lambda (x)
                (format #t "~a: ~a~%" (ref* x 'user 'name) (ref x 'text)))
              list)
    list))

あ、備前さんのアドバイスにより -*- coding: utf-8 -*- をつけたはいいものの、こんどは日本語コメントが無くなってしまった。