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>
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
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
41 ;; There are four main interactive functions:
43 ;; M-x twit-post RET will prompt for you to type your post directly
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
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
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.
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.
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
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,
94 ;; `twit-user' gets my vote for variable name of the year. Ditto
95 ;; `twit-mode' for mode names.
99 ;; 2007-3-16 theron tlax <thorne@timbral.net>
100 ;; * 0.0.1 -- Initial release. Posting only.
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)
123 ;; * Posts with semicolons are being silently truncated. I don't
126 ;; `twit-list-followers' may not work if it is the first thing you
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
144 ;; Finish reading, and then add a timer for auto-update.
152 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
)
167 (define-key twit-status-mode-map
(car info
) (cdr info
))
168 (define-key twit-followers-mode-map
(car info
) (cdr info
)))
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
202 (copy-face 'bold
'twit-message-face
)
203 (set-face-attribute 'twit-message-face nil
207 :width
'semi-condensed
)
208 (copy-face 'bold
'twit-author-face
)
209 (set-face-attribute 'twit-author-face nil
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."
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)))
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 ()
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>")
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"
278 (concat " (" user-name
")")
280 '((face .
"twit-author-face")))
282 (twit-insert-with-overlay-attributes message
283 '((face .
"twit-message-face")))
285 (when (or timestamp location src-info
)
288 (insert (concat " posted " timestamp
)))
290 (insert (concat " from " location
)))
292 (insert (concat " (via " src-info
")")))
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"
300 (set-buffer "*Twit-recent*")
302 (twit-write-recent-tweets)
303 (toggle-read-only 1)))
305 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
306 ;;; Main interactive functions
307 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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
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
)))))
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
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
)))))
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
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
)))))
350 (defun twit-list-followers ()
351 "Display a list of all your twitter.com followers' names."
353 (pop-to-buffer "*Twit-followers*")
354 (kill-region (point-min) (point-max))
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
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)))
372 (let ((overlay (make-overlay start
(point))))
373 (dolist (spec attributes
)
374 (overlay-put overlay
(car spec
) (cdr spec
))))))
377 ;;; Added by Jonathan Arkell
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."
382 (twit-show-recent-tweets)
383 (setq twit-timer
(run-with-idle-timer twit-follow-idle-interval
1 'twit-follow-recent-tweets-timer
)))
386 (defun twit-show-recent-tweets ()
387 "Display a list of the most recent twewets from your followers."
389 (pop-to-buffer "*Twit-recent*")
391 (twit-write-recent-tweets)
392 ;; set up some sensible mode and useful bindings
395 (use-local-map twit-status-mode-map
))
398 (define-minor-mode 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
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
))
414 :version twit-version-number
)
418 ;;; twit.el ends here