changelog shortlog tags changeset files revisions annotate raw

twitter.el

changeset 66: 5b737eefe5ea
author: kim.vanwyk
date: Wed Nov 10 15:19:03 2010 +0200 (18 months ago)
permissions: -rw-r--r--
description: Adding CSharp Mode and Google Weather
1;;; twitter.el --- Simple Emacs-based client for Twitter
2
3;; Author: Neil Roberts
4;; Keywords: twitter
5
6;; Copyright 2008 Neil Roberts
7;;
8;; This program is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation; either version 2, or (at your option)
11;; any later version.
12;;
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17;;
18;; You should have received a copy of the GNU General Public License
19;; along with this program; if not, write to the Free Software
20;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21;; 02110-1301, USA.
22
23;;; Commentary:
24
25;; A Twitter client for emacs that can view your friends timeline and
26;; publish new statuses.
27
28;;; Your should add the following to your Emacs configuration file:
29
30;; (autoload 'twitter-get-friends-timeline "twitter" nil t)
31;; (autoload 'twitter-status-edit "twitter" nil t)
32;; (global-set-key "\C-xt" 'twitter-get-friends-timeline)
33;; (add-hook 'twitter-status-edit-mode-hook 'longlines-mode)
34
35;; Tell it your username and password by customizing the group
36;; "twitter".
37
38;; you can view the statuses by pressing C-x t and you can start
39;; editing a message with M-x twitter-status-edit RET. Once the
40;; message is finished press C-c C-c to publish.
41
42;;; Code:
43(require 'url)
44(require 'url-http)
45(require 'xml)
46
47(defgroup twitter nil "Twitter status viewer"
48 :group 'applications)
49
50(defgroup twitter-faces nil "Faces for displaying Twitter statuses"
51 :group 'twitter)
52
53(defface twitter-user-name-face
54 '((t (:weight bold :background "light gray")))
55 "face for user name headers"
56 :group 'twitter-faces)
57
58(defface twitter-time-stamp-face
59 '((t (:slant italic :background "light gray")))
60 "face for time stamp headers"
61 :group 'twitter-faces)
62
63(defface twitter-status-overlong-face
64 '((t (:foreground "red")))
65 "face used for characters in overly long Twitter statuses.
66The face is also used in the mode line if the character count
67remaining drops to negative.")
68
69(defconst twitter-friends-timeline-url
70 "http://twitter.com/statuses/friends_timeline.xml"
71 "URL used to receive the friends timeline")
72
73(defconst twitter-status-update-url
74 "http://twitter.com/statuses/update.xml"
75 "URL used to update Twitter status")
76
77(defcustom twitter-username nil
78 "Username to use for connecting to Twitter.
79If nil, you will be prompted."
80 :type '(choice (const :tag "Ask" nil) (string))
81 :group 'twitter)
82
83(defcustom twitter-password nil
84 "Password to use for connecting to Twitter.
85If nil, you will be prompted."
86 :type '(choice (const :tag "Ask" nil) (string))
87 :group 'twitter)
88
89(defcustom twitter-maximum-status-length 140
90 "Maximum length to allow in a Twitter status update."
91 :type 'integer
92 :group 'twitter)
93
94(defvar twitter-status-edit-remaining-length ""
95 "Characters remaining in a Twitter status update.
96This is displayed in the mode line.")
97
98(put 'twitter-status-edit-remaining-length 'risky-local-variable t)
99
100(defvar twitter-status-edit-overlay nil
101 "Overlay used to highlight overlong status messages.")
102
103(defvar twitter-status-edit-mode-map
104 (let ((map (make-sparse-keymap)))
105 (set-keymap-parent map text-mode-map)
106 (define-key map "\C-c\C-c" 'twitter-status-post)
107 map)
108 "Keymap for `twitter-status-edit-mode'.")
109
110(defun twitter-retrieve-url (url cb)
111 "Wrapper around url-retrieve.
112Optionally sets the username and password if twitter-username and
113twitter-password are set."
114 (when (and twitter-username twitter-password)
115 (let ((server-cons
116 (or (assoc "twitter.com:80" url-http-real-basic-auth-storage)
117 (car (push (cons "twitter.com:80" nil) url-http-real-basic-auth-storage)))))
118 (unless (assoc "Twitter API" server-cons)
119 (setcdr server-cons (cons (cons "Twitter API"
120 (base64-encode-string (concat twitter-username
121 ":" twitter-password)))
122 (cdr server-cons))))))
123 (url-retrieve url cb))
124
125(defun twitter-get-friends-timeline ()
126 "Fetch and display the friends timeline.
127The results are formatted and displayed in a buffer called
128*Twitter friends timeline*"
129 (interactive)
130 (twitter-retrieve-url twitter-friends-timeline-url
131 'twitter-fetched-friends-timeline))
132
133(defun twitter-fetched-friends-timeline (status &rest cbargs)
134 "Callback handler for fetching the Twitter friends timeline."
135 (let ((result-buffer (current-buffer)) doc)
136 ;; Make sure the temporary results buffer is killed even if the
137 ;; xml parsing raises an error
138 (unwind-protect
139 (progn
140 ;; Skip the mime headers
141 (goto-char (point-min))
142 (re-search-forward "\n\n")
143 ;; Parse the rest of the document
144 (setq doc (xml-parse-region (point) (point-max))))
145 (kill-buffer result-buffer))
146 ;; Get a clean buffer to display the results
147 (let ((buf (get-buffer-create "*Twitter friends timeline*")))
148 (with-current-buffer buf
149 (let ((inhibit-read-only t))
150 (erase-buffer)
151 (kill-all-local-variables)
152 ;; If the GET failed then display an error instead
153 (if (plist-get status :error)
154 (twitter-show-error doc)
155 ;; Otherwise process each status node
156 (mapcar 'twitter-format-status-node (xml-get-children (car doc) 'status))))
157 (goto-char (point-min)))
158 (view-buffer buf))))
159
160(defun twitter-get-node-text (node)
161 "Return the text of XML node NODE.
162All of the text elements are concatenated together and returned
163as a single string."
164 (let (text-parts)
165 (dolist (part (xml-node-children node))
166 (when (stringp part)
167 (push part text-parts)))
168 (apply 'concat (nreverse text-parts))))
169
170(defun twitter-get-attrib-node (node attrib)
171 "Get the text of a child attribute node.
172If the children of XML node NODE are formatted like
173<attrib1>data</attrib1> <attrib2>data</attrib2> ... then this
174fuction will return the text of the child node named ATTRIB or
175nil if it isn't found."
176 (let ((child (xml-get-children node attrib)))
177 (if (consp child)
178 (twitter-get-node-text (car child))
179 nil)))
180
181(defun twitter-show-error (doc)
182 "Show a Twitter error message.
183DOC should be the XML parsed document returned in the error
184message. If any information about the error can be retrieved it
185will also be displayed."
186 (insert "An error occured while trying to process a Twitter request.\n\n")
187 (let (error-node)
188 (if (and (consp doc)
189 (consp (car doc))
190 (eq 'hash (caar doc))
191 (setq error-node (xml-get-children (car doc) 'error)))
192 (insert (twitter-get-node-text (car error-node)))
193 (xml-print doc))))
194
195(defun twitter-format-status-node (status-node)
196 "Insert the contents of a Twitter status node.
197The status is formatted with text properties and insterted into
198the current buffer."
199 (let ((user-node (xml-get-children status-node 'user)) val)
200 (when user-node
201 (setq user-node (car user-node))
202 (when (setq val (twitter-get-attrib-node user-node 'name))
203 (insert (propertize val 'face 'twitter-user-name-face))))
204 (when (setq val (twitter-get-attrib-node status-node 'created_at))
205 (when (< (+ (current-column) (length val)) fill-column)
206 (setq val (concat (make-string (- fill-column
207 (+ (current-column) (length val))) ? )
208 val)))
209 (insert (propertize val 'face 'twitter-time-stamp-face)))
210 (insert "\n")
211 (when (setq val (twitter-get-attrib-node status-node 'text))
212 (fill-region (prog1 (point) (insert val)) (point)))
213 (insert "\n\n")))
214
215(defun twitter-status-post ()
216 "Update your Twitter status.
217The contents of the current buffer are used for the status. The
218current buffer is then killed. If there is too much text in the
219buffer then you will be asked for confirmation."
220 (interactive)
221 (when (or (<= (buffer-size) twitter-maximum-status-length)
222 (y-or-n-p (format (concat "The message is %i characters long. "
223 "Are you sure? ") (buffer-size))))
224 (message "Sending status...")
225 (let ((url-request-method "POST")
226 (url-request-data (concat "status="
227 (url-hexify-string (buffer-substring
228 (point-min) (point-max))))))
229 (twitter-retrieve-url twitter-status-update-url 'twitter-status-callback))))
230
231(defun twitter-status-callback (status)
232 "Function called after Twitter status has been sent."
233 (let ((errmsg (plist-get status :error)))
234 (when errmsg
235 (signal (car errmsg) (cdr errmsg)))
236 (kill-buffer "*Twitter Status*")
237 (message "Succesfully updated Twitter status.")))
238
239(defun twitter-status-edit ()
240 "Edit your twitter status in a new buffer.
241A new buffer is popped up in a special edit mode. Press
242\\[twitter-status-post] when you are finished editing to send the
243message."
244 (interactive)
245 (pop-to-buffer "*Twitter Status*")
246 (twitter-status-edit-mode))
247
248(defun twitter-status-edit-update-length ()
249 "Updates the character count in Twitter status buffers.
250This should be run after the text in the buffer is changed. Any
251characters after the maximum status update length are
252hightlighted in the face twitter-status-overlong-face and the
253character count on the mode line is updated."
254 ;; Update the remaining characters in the mode line
255 (let ((remaining (- twitter-maximum-status-length
256 (buffer-size))))
257 (setq twitter-status-edit-remaining-length
258 (concat " "
259 (if (>= remaining 0)
260 (number-to-string remaining)
261 (propertize (number-to-string remaining)
262 'face 'twitter-status-overlong-face))
263 " ")))
264 (force-mode-line-update)
265 ;; Highlight the characters in the buffer that are over the limit
266 (if (> (buffer-size) twitter-maximum-status-length)
267 (let ((start (+ (point-min) twitter-maximum-status-length)))
268 (if (null twitter-status-edit-overlay)
269 (overlay-put (setq twitter-status-edit-overlay
270 (make-overlay start (point-max)))
271 'face 'twitter-status-overlong-face)
272 (move-overlay twitter-status-edit-overlay
273 start (point-max))))
274 ;; Buffer is not too long so just hide the overlay
275 (when twitter-status-edit-overlay
276 (delete-overlay twitter-status-edit-overlay))))
277
278(defun twitter-status-edit-after-change (begin end old-size)
279 (twitter-status-edit-update-length))
280
281(define-derived-mode twitter-status-edit-mode text-mode "Twitter Status Edit"
282 "Major mode for updating your Twitter status."
283 ;; Schedule to update the character count after altering the buffer
284 (make-local-variable 'after-change-functions)
285 (add-hook 'after-change-functions 'twitter-status-edit-after-change)
286 ;; Add the remaining character count to the mode line
287 (make-local-variable 'twitter-status-edit-remaining-length)
288 ;; Copy the mode line format list so we can safely edit it without
289 ;; affecting other buffers
290 (setq mode-line-format (copy-sequence mode-line-format))
291 ;; Add the remaining characters variable after the mode display
292 (let ((n mode-line-format))
293 (catch 'found
294 (while n
295 (when (eq 'mode-line-modes (car n))
296 (setcdr n (cons 'twitter-status-edit-remaining-length
297 (cdr n)))
298 (throw 'found nil))
299 (setq n (cdr n)))))
300 ;; Make a buffer-local reference to the overlay for overlong
301 ;; messages
302 (make-local-variable 'twitter-status-edit-overlay)
303 ;; Update the mode line immediatly
304 (twitter-status-edit-update-length))
305
306(provide 'twitter)
307
308;;; twitter.el ends here