(require 'xml)
(require 'url)
(defgroup twitter nil
"Twitter client."
:prefix "twitter-"
:group 'hypermedia)
(defcustom twitter-username
nil
"Twitter username. Will prompt if not set."
:type 'string
:group 'twitter)
(defcustom twitter-password
nil
"Twitter password. Will prompt if not set"
:type 'string
:group 'twitter)
(defcustom twitter-public-timeline-buffer-name
"*Twitter-Public-Timeline*"
"Buffer name to use for twitter public timeline."
:type 'string
:group 'twitter)
(defcustom twitter-friends-timeline-buffer-name
"*Twitter-Friends-Timeline*"
"Buffer name to use for twitter friends timeline."
:type 'string
:group 'twitter)
(defcustom twitter-friends-buffer-name
"*Twitter-Friends*"
"Buffer name to use for twitter friends."
:type 'string
:group 'twitter)
(defcustom twitter-followers-buffer-name
"*Twitter-Followers*"
"Buffer name to use for twitter followers."
:type 'string
:group 'twitter)
(copy-face 'bold 'twitter-message-face)
(set-face-attribute 'twitter-message-face nil
:family "helv"
:height 1.2
:weight 'semi-bold
:width 'semi-condensed)
(copy-face 'bold 'twitter-author-face)
(set-face-attribute 'twitter-author-face nil
:family 'unspecified
:weight 'semi-bold
:width 'semi-condensed)
(defvar twitter-base-url "http://twitter.com/")
(defvar twitter-api-urls
(mapcar (lambda (x) (list (car x) (concat twitter-base-url (cdr x))))
'((:public-timeline . "statuses/public_timeline.xml")
(:friends-timeline . "statuses/friends_timeline.xml")
(:friends . "statuses/friends.xml")
(:followers . "statuses/followers.xml")
(:update . "statuses/update.xml")))
"URLs for the twitter API.")
(defun twitter-request (type &optional username password message)
"Submit an API request to twitter.
\nTYPE is an API type found in `twitter-api-urls'.
USERNAME is the twitter username, if nil `twitter-username' is used.
PASSWORD is the twitter password, if nil `twitter-password' is used.
MESSAGE is the string to post if doing an `:update'."
(interactive
(list (or username twitter-username (read-from-minibuffer "Twitter username: " ""))
(or password twitter-password (read-from-minibuffer "Twitter password: " ""))
(and (eq type :update) (or message (read-from-minibuffer "Message: " "")))))
(let ((url (cadr (assq type twitter-api-urls)))
(url-request-method "GET")
(url-request-data "")
result)
(unless url
(error "Invalid type given: %s" type))
(save-current-buffer
(if (eq type :update)
(progn
(when (> (length message) 140)
(error "Twitter message cannot be longer than 140 characters"))
(setq url-request-data (concat "status=" (url-hexify-string message)))
(message "%s" url-request-data)
(url-retrieve url (lambda (x) (kill-buffer (current-buffer))))
(setq result t))
(progn
(set-buffer (url-retrieve-synchronously url))
(goto-char (point-min))
(setq result (xml-parse-region))
(kill-buffer (current-buffer)))))
result))
(defun twitter-parse-result (result)
"Parse result XML returned from `twitter-request' into current buffer."
(cl-labels ((insert-with-overlay-attributes (text attributes)
(let ((start (point)))
(insert text)
(let ((overlay (make-overlay start (point))))
(dolist (spec attributes)
(overlay-put overlay (car spec) (cdr spec))))))
(xml-first-child (node attr)
(car (xml-get-children node attr)))
(xml-first-childs-value (node addr)
(car (xml-node-children (xml-first-child node addr)))))
(save-excursion
(erase-buffer)
(insert (format-time-string "Last updated: %c\n\n"))
(dolist (status-node (xml-get-children (cadr result) 'status))
(let* ((user-info (xml-first-child status-node 'user))
(user-id (or (xml-first-childs-value user-info 'screen_name) "??"))
(user-name (xml-first-childs-value user-info 'name))
(location (xml-first-childs-value user-info 'location))
(src-info (xml-first-childs-value status-node 'source))
(timestamp (xml-first-childs-value status-node 'created_at))
(message (xml-first-childs-value status-node 'text))
)
(when (and src-info (string-match (concat "<a h" "ref=.*>\\(.*\\)<" "/a>") src-info))
(setq src-info (match-string 1 src-info)))
(insert-with-overlay-attributes (format "%s" (concat user-id
(if user-name
(concat " (" user-name ")")
"")))
'((face . "twitter-author-face")))
(newline)
(let ((pos (point)))
(insert " ")
(insert-with-overlay-attributes message
'((face . "twitter-message-face")))
(fill-region pos (point)))
(newline)
(when (or timestamp location src-info)
(insert " ")
(when timestamp
(insert (concat " posted " timestamp)))
(when location
(insert (concat " from " location)))
(when src-info
(insert (concat " (via " src-info ")")))
(newline))
(newline))))))
(defun twitter-update (&optional message username password)
"Submit an update MESSAGE to twitter.
\nUSERNAME is the twitter username, if nil `twitter-username' is used.
PASSWORD is the twitter password, if nil `twitter-password' is used."
(interactive
(list (or message (read-from-minibuffer "Message: " ""))
(or username twitter-username (read-from-minibuffer "Twitter username: " ""))
(or password twitter-password (read-from-minibuffer "Twitter password: " ""))))
(twitter-request :update username password message))
(defun twitter-show (type buffer-name &optional username password)
"Show twitter request in a buffer.
\nUSERNAME is the twitter username, if nil `twitter-username' is used.
PASSWORD is the twitter password, if nil `twitter-password' is used."
(get-buffer-create buffer-name)
(set-buffer buffer-name)
(setq buffer-read-only nil)
(twitter-parse-result (twitter-request type username password))
(setq buffer-read-only t)
(switch-to-buffer buffer-name)
(goto-char (point-min)))
(defun twitter-show-public-timeline ()
"Display the twitter public timeline in a buffer."
(interactive)
(twitter-show :public-timeline twitter-public-timeline-buffer-name))
(provide 'twitter)