2010年3月28日日曜日

ClojureでTwitter

Clojureの練習がてらTwitterクライアントの作成を目指す。

取り合えず、タイムラインを取得してJTableで表示してみた。

(require 'clojure.contrib.http.agent)
(import java.net.URLEncoder sun.misc.BASE64Encoder)
(import '(javax.swing.table AbstractTableModel))

(def status-list (atom []))

(defn seq->map [seq]
(reduce
(fn [map [key val]]
(assoc map key val))
{}
seq))

(defn basic-authentication [id pass]
(str "Basic "
(.encode (BASE64Encoder.)
(.getBytes (str id ":" pass)))))

(defn xml-request
([uri] (xml-request uri {}))
([uri headers]
(clojure.xml/parse
(clojure.contrib.http.agent/stream
(clojure.contrib.http.agent/http-agent
uri
:headers headers)))))

(defn xml-request-with-auth
([uri auth] (xml-request-with-auth uri auth {}))
([uri auth headers]
(xml-request
uri
(merge headers {"Authorization" auth} ))))

(defn collect-from-status [{tag :tag content :content :as status} & tags]
(filter
(fn [st] (some #(= % (:tag st)) tags))
content))

(defn collect-default-elements [status]
(seq->map
(map
(fn [st] [(:tag st) (first (:content st))])
(concat
(collect-from-status
status
:text
:created_at
:id
:in_reply_to_status_id)
(collect-from-status
(first (collect-from-status status :user))
:name
:screen_name
:profile_image_url
:location
:description)))))

;;例:"Sun Mar 28 00:18:31 +0000 2010"
(defn time->number [time-str]
(let [[week month day hour minitu sec _ year]
(re-seq #"\w+" time-str)
m-num
({"Jan" 1, "Feb" 2, "Mar" 3, "Apr" 4, "May" 5,
"Jun" 6, "Jul" 7, "Aug" 8, "Sep" 9, "Oct" 10,
"Nov" 11, "Dec" 12}
month)]
(+
(* (Integer/parseInt year) 10000000000)
(* m-num 100000000)
(* (Integer/parseInt day) 1000000)
(* (Integer/parseInt hour) 10000)
(* (Integer/parseInt minitu) 100)
(Integer/parseInt sec))))

(defn sort-status-list [statuses]
(sort #(> (time->number (:created_at %1)) (time->number (:created_at %2)))
(map collect-default-elements statuses)))

(defn update-timeline [statuses id pass]
(let [since-id (:id (first @statuses))]
(reset! statuses
(concat
(sort-status-list
(:content
(xml-request-with-auth
(if (nil? since-id)
"http://twitter.com/statuses/home_timeline.xml"
(str
"http://twitter.com/statuses/home_timeline.xml?"
since-id))
(basic-authentication id pass)
{})))
@statuses))))

(defn model [column-names statuses]
(proxy [AbstractTableModel] []
(getRowCount [] (count @statuses))
(getValueAt [row col]
(if (= col 0)
(:screen_name (nth @statuses row))
(:text (nth @statuses row))))
(getColumnName [c](print (nth column-names c))
(nth column-names c))
(getColumnCount []
(count column-names))
(isCellEditable [r c] false)))

;;atomであるstatus-listの内容を表示する
(defn run []
(let [f (javax.swing.JFrame. "Test")
m (model ["name" "本文"] status-list)
tbl (javax.swing.JTable. m)]
(doto f
(.setSize 300 300)
(.setVisible true))
(doto tbl
(.setVisible true))
(.. f getContentPane
(add (new javax.swing.JScrollPane tbl)))))

;;(update-timeline status-list "id" "pass")
;;(run)

0 件のコメント:

コメントを投稿