use cooper theme -- end of git, I am trying livemesh
[srid.dotfiles.git] / emacs / external / twit.el
blob259c456ec2b9e7af2959e0b834213b51e0606c3b
1 ;;; twit.el --- interface with twitter.com
3 ;; Copyright (c) 2007 Theron Tlax
4 ;; Time-stamp: <2007-03-19 18:33:17 thorne>
5 ;; Author: thorne <thorne@timbral.net>
6 ;; Created: 2007.3.16
7 ;; Keywords: comm
8 ;; Favorite Poet: E. E. Cummings
10 ;; This file is not part of GNU Emacs.
12 ;; This program is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation version 2.
16 ;; This program is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ;; General Public License for more details.
21 ;; For a copy of the GNU General Public License, search the Internet,
22 ;; or write to the Free Software Foundation, Inc., 59 Temple Place,
23 ;; Suite 330, Boston, MA 02111-1307 USA
25 ;;; Commentary:
27 ;; This is the beginnings of a library for interfacing with
28 ;; twitter.com from Emacs. It is also (more importantly) some
29 ;; interactive functions that use that library. It's a hack, of
30 ;; course; RMS i am not. Maybe one of you real programmers would
31 ;; like to clean it up?
33 ;; This uses Twitter's XML-based api, not the JSON one because i
34 ;; would like to avoid making the user install third-party libraries
35 ;; to use it.
37 ;; Use:
39 ;; FOR POSTING
41 ;; There are four main interactive functions:
43 ;; M-x twit-post RET will prompt for you to type your post directly
44 ;; in the minibuffer.
46 ;; M-x twit-post-region RET will post the region and
48 ;; M-x twit-post-buffer RET will post the entire contents of the
49 ;; current buffer.
51 ;; M-X twit-show-recent-tweets RET will create a new buffer and
52 ;; show your most recent messages in it.
54 ;; M-x twit-mode RET, if you want to bother, just binds the
55 ;; interactive functions to some keys. Do C-h f RET twit-mode RET
56 ;; for more info.
58 ;; M-x twit-follow-recent-tweets RET will create a new buffer,
59 ;; show the most recent tweets, and update it every 90 seconds (idle)
61 ;; But remember that your posts can't be longer than 140 characters
62 ;; long. All of these functions will also prompt you for your user
63 ;; name (usually the email address you signed up to twitter with)
64 ;; and password the first time in a given Emacs session. Note that
65 ;; twitter uses `Basic Authentication' for user authentication,
66 ;; which translates to, basically none. It's not secure for
67 ;; anything more than casual attacks.
69 ;; FOR READING
71 ;; This is a work in progress. Just stubs. I have to figure out
72 ;; how to make some use out of `xml-parse-fragment'. Until then,
73 ;; `twit-list-followers' is incredibly stupid, but works.
75 ;; FOR HACKING
77 ;; See `twit-post-function', which is the backend for posting, and
78 ;; `twit-parse-xml' which grabs an xml file from HTTP and turns it
79 ;; into a list structure (using `xml-parse-fragment'). This is a work
80 ;; in progress.
82 ;; Installing:
84 ;; There's not much to it. It you want it always there and ready, you
85 ;; can add something to your .emacs file like:
87 ;; (load-file "/path/to/twit.el")
89 ;; or get fancier, to the extent you want and know how (autoloading,
90 ;; keybinding, etc).
92 ;; Notes:
94 ;; `twit-user' gets my vote for variable name of the year. Ditto
95 ;; `twit-mode' for mode names.
97 ;;; History:
99 ;; 2007-3-16 theron tlax <thorne@timbral.net>
100 ;; * 0.0.1 -- Initial release. Posting only.
101 ;; 2007-3-17 ''
102 ;; * 0.0.2 -- Near-total rewrite; better documentation; use standard
103 ;; Emacs xml and url packages; minor mode; a little
104 ;; abstraction; some stubs for the reading functions.
105 ;; * 0.0.3 -- Doc and other minor changes.
106 ;; * 0.0.4 -- (released as 0.0.3 -- Added twit-show-recent-tweets
107 ;; by Jonathan Arkell)
108 ;; * 0.0.5 -- Add source parameter to posts
109 ;; * 0.0.6 -- Re-working twit-show-recent-tweets to show more info
110 ;; (and to get it working for me) -- by H Durer
111 ;; * 0.0.7 -- Keymaps in the buffers for twit-show-recent-tweets and
112 ;; twit-list-followers; encode the post argument so that it
113 ;; is a valid post request
114 ;; * 0.0.8 -- faces/overlays to make the *Twit-recent* buffer look
115 ;; prettier and more readable (at least for me) -- by H Durer
116 ;; * 0.0.9 -- follow-recent-tweets function created so automagickally
117 ;; follow tweets every 5 mins. Also removed twit-mode
118 ;; on twit-show-recent-tweets. (it was setting twit-mode
119 ;; globally, and interfering with planner)
121 ;; Bugs:
123 ;; * Posts with semicolons are being silently truncated. I don't
124 ;; know why.
126 ;; `twit-list-followers' may not work if it is the first thing you
127 ;; do.
129 ;; Report bugs to me at the listed email address. Additionally,
130 ;; report the absence of bugs if you are using a system not in the
131 ;; list below of systems tested at least minimally:
133 ;; Twit 0.0.2 / Emacs 22.0.93.1 / windows-nt
134 ;; Twit 0.0.2 / Emacs 23.0.51.1 / gnu/linux
135 ;; Twit 0.0.3 / Emacs 22..92.1 / gnu/linux
136 ;; Twit 0.0.6 / Emacs 22.0.90.1 / gnu/linux
137 ;; Twit 0.0.8 / Emacs 22.1.1 / gnu/linux
138 ;; Twit 0.0.8 / Emacs 22.0.99.1 / windows
142 ;;; To do:
144 ;; Finish reading, and then add a timer for auto-update.
147 ;;; Code:
149 (require 'xml)
150 (require 'url)
152 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
153 ;;; Variables
154 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
156 (defvar twit-version-number "0.0.8")
158 (defvar twit-status-mode-map (make-sparse-keymap))
159 (defvar twit-followers-mode-map (make-sparse-keymap))
161 ;; 'r' key for reloading/refreshing the buffer
162 (define-key twit-status-mode-map "r" 'twit-show-recent-tweets)
163 (define-key twit-followers-mode-map "r" 'twit-list-followers)
164 (dolist (info '(("s" . twit-show-recent-tweets)
165 ("f" . twit-list-followers)
166 ("p" . twit-post)))
167 (define-key twit-status-mode-map (car info) (cdr info))
168 (define-key twit-followers-mode-map (car info) (cdr info)))
171 (defvar twit-timer
173 "Timer object that handles polling the followers")
176 ;; Most of this will be used in the yet-to-be-written twitter
177 ;; reading functions.
178 (defvar twit-base-url "http://twitter.com")
180 (defconst twit-follow-idle-interval 90)
182 (defconst twit-update-url
183 (concat twit-base-url "/statuses/update.xml"))
184 (defconst twit-puplic-timeline-file
185 (concat twit-base-url "/statuses/public_timeline.xml"))
186 (defconst twit-friend-timeline-file
187 (concat twit-base-url "/statuses/friends_timeline.xml"))
188 (defconst twit-followers-file
189 (concat twit-base-url "/statuses/followers.xml"))
190 (defconst twit-friend-list-file
191 (concat twit-base-url "/statuses/friends.xml"))
193 (defconst twit-success-msg
194 "Post sent (no guarantees, though)")
195 (defconst twit-too-long-msg
196 "Post not sent because length exceeds 140 characters")
198 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
199 ;; Faces
200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
202 (copy-face 'bold 'twit-message-face)
203 (set-face-attribute 'twit-message-face nil
204 :family "helv"
205 :height 1.2
206 :weight 'semi-bold
207 :width 'semi-condensed)
208 (copy-face 'bold 'twit-author-face)
209 (set-face-attribute 'twit-author-face nil
210 :family 'unspecified
211 :weight 'semi-bold
212 :width 'semi-condensed)
216 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
217 ;;; General purpose library to wrap twitter.com's api
218 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
220 (defun twit-parse-xml (url)
221 "Retrieve file at URL and parse with `xml-parse-fragment'.
222 Emacs' url package will prompt for authentication info if required."
223 (let ((result nil))
224 (save-window-excursion
225 (set-buffer (url-retrieve-synchronously url))
226 (goto-char (point-min))
227 (setq result (xml-parse-fragment))
228 (kill-buffer (current-buffer)))
229 result))
231 (defun twit-post-function (url post)
232 (let ((url-request-method "POST")
233 (url-request-data (concat "source=twit.el&status=" (url-hexify-string post)))
234 ;; these headers don't actually do anything (yet?) -- the
235 ;; source parameter above is what counts
236 (url-request-extra-headers `(("X-Twitter-Client" . "twit.el")
237 ("X-Twitter-Client-Version" . ,twit-version-number)
238 ("X-Twitter-Client-URL" . "http://www.emacswiki.org/cgi-bin/emacs/twit.el"))))
239 (message "%s" url-request-data)
240 (url-retrieve url (lambda (arg) (kill-buffer (current-buffer))))))
243 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
244 ;;; Helpers for the interactive functions
245 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
247 (defun twit-query-for-post ()
248 "Query for a Twitter.com post text in the minibuffer."
249 (read-string "Post (140 char max): "))
252 (defun twit-write-recent-tweets ()
253 (save-excursion
254 (delete-region (point-min) (point-max))
255 (insert (format-time-string "Last updated: %c\n"))
256 (labels ((xml-first-child (node attr)
257 (car (xml-get-children node attr)))
258 (xml-first-childs-value (node addr)
259 (car (xml-node-children (xml-first-child node addr)))))
260 (dolist (status-node (xml-get-children (cadr (twit-parse-xml twit-friend-timeline-file)) 'status))
261 (let* ((user-info (xml-first-child status-node 'user))
262 (user-id (or (xml-first-childs-value user-info 'screen_name) "??"))
263 (user-name (xml-first-childs-value user-info 'name))
264 (location (xml-first-childs-value user-info 'location))
265 (src-info (xml-first-childs-value status-node 'source))
266 (timestamp (xml-first-childs-value status-node 'created_at))
267 (message (xml-first-childs-value status-node 'text)))
268 ;; the string-match is a bit weird, as emacswiki.org won't
269 ;; accept pages with the href in it per se
270 (when (and src-info (string-match (concat "<a h" "ref=.*>\\(.*\\)<" "/a>")
271 src-info))
272 ;; remove the HTML link info; leave just the name
273 (setq src-info (match-string 1 src-info)))
274 ;; First line: Name and message
275 (twit-insert-with-overlay-attributes (format "%25s"
276 (concat user-id
277 (if user-name
278 (concat " (" user-name ")")
279 "")))
280 '((face . "twit-author-face")))
281 (insert ": ")
282 (twit-insert-with-overlay-attributes message
283 '((face . "twit-message-face")))
284 (insert "\n")
285 (when (or timestamp location src-info)
286 (insert " ")
287 (when timestamp
288 (insert (concat " posted " timestamp)))
289 (when location
290 (insert (concat " from " location)))
291 (when src-info
292 (insert (concat " (via " src-info ")")))
293 (insert "\n")))))
294 ;; go back to top so we see the latest messages
295 (beginning-of-buffer)))
297 (defun twit-follow-recent-tweets-timer ()
298 "Timer function for recent tweets"
299 (save-excursion
300 (set-buffer "*Twit-recent*")
301 (toggle-read-only 0)
302 (twit-write-recent-tweets)
303 (toggle-read-only 1)))
305 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
306 ;;; Main interactive functions
307 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
309 ;;;###autoload
310 (defun twit-post ()
311 "Send a post to twitter.com.
312 Prompt the first time for password and username \(unless
313 `twit-user' and/or `twit-pass' is set\) and for the text of the
314 post; thereafter just for post text. Posts must be <= 140 chars
315 long."
316 (interactive)
317 (let* ((post (twit-query-for-post)))
318 (if (> (length post) 140)
319 (error twit-too-long-msg)
320 (if (twit-post-function twit-update-url post)
321 (message twit-success-msg)))))
323 ;;;###autoload
324 (defun twit-post-region (start end)
325 "Send text in the region as a post to twitter.com.
326 Uses `twit-post-function' to do the dirty work and to obtain
327 needed user and password information. Posts must be <= 140 chars
328 long."
329 (interactive "r")
330 (let ((post (buffer-substring start end)))
331 (if (> (length post) 140)
332 (error twit-too-long-error)
333 (if (twit-post-function twit-update-url post)
334 (message twit-success-msg)))))
336 ;;;###autoload
337 (defun twit-post-buffer ()
338 "Post the entire contents of the current buffer to twitter.com.
339 Uses `twit-post-function' to do the dirty work and to obtain
340 needed user and password information. Posts must be <= 140 chars
341 long."
342 (interactive)
343 (let ((post (buffer-substring (point-min) (point-max))))
344 (if (> (length post) 140)
345 (error twit-too-long-error)
346 (if (twit-post-function twit-update-url post)
347 (message twit-success-msg)))))
349 ;;;###autoload
350 (defun twit-list-followers ()
351 "Display a list of all your twitter.com followers' names."
352 (interactive)
353 (pop-to-buffer "*Twit-followers*")
354 (kill-region (point-min) (point-max))
355 (loop for name in
356 (loop for name in
357 (loop for user in
358 (xml-get-children
359 (cadr (twit-parse-xml twit-followers-file)) 'user)
360 collect (sixth user))
361 collect (third name))
362 do (insert (concat name "\n")))
363 ;; set up mode as with twit-show-recent-tweets
364 (text-mode)
365 (use-local-map twit-followers-mode-map))
367 ;;; Helper function to insert text into buffer, add an overlay and
368 ;;; apply the supplied attributes to the overlay
369 (defun twit-insert-with-overlay-attributes (text attributes)
370 (let ((start (point)))
371 (insert text)
372 (let ((overlay (make-overlay start (point))))
373 (dolist (spec attributes)
374 (overlay-put overlay (car spec) (cdr spec))))))
377 ;;; Added by Jonathan Arkell
378 ;;;###autoload
379 (defun twit-follow-recent-tweets ()
380 "Display, and redisplay the tweets. This might suck if it bounces the point to the bottom all the time."
381 (interactive)
382 (twit-show-recent-tweets)
383 (setq twit-timer (run-with-idle-timer twit-follow-idle-interval 1 'twit-follow-recent-tweets-timer)))
385 ;;;###autoload
386 (defun twit-show-recent-tweets ()
387 "Display a list of the most recent twewets from your followers."
388 (interactive)
389 (pop-to-buffer "*Twit-recent*")
390 (toggle-read-only 0)
391 (twit-write-recent-tweets)
392 ;; set up some sensible mode and useful bindings
393 (text-mode)
394 (toggle-read-only 1)
395 (use-local-map twit-status-mode-map))
397 ;;;###autoload
398 (define-minor-mode twit-mode
399 "Toggle twit-mode.
400 Globally binds some keys to Twit's interactive functions.
402 With no argument, this command toggles the mode.
403 Non-null prefix argument turns on the mode.
404 Null prefix argument turns off the mode.
406 \\{twit-mode-map}" nil
407 " Twit"
408 '(("\C-c\C-tp" . twit-post)
409 ("\C-c\C-tr" . twit-post-region)
410 ("\C-c\C-tb" . twit-post-buffer)
411 ("\C-c\C-tf" . twit-list-followers))
412 :global t
413 :group 'twit
414 :version twit-version-number)
416 (provide 'twit)
418 ;;; twit.el ends here