root/lang/elisp/twittering-mode/trunk/twittering-mode.el @ 137

Revision 137, 39.2 kB (checked in by tsuyoshi, 15 years ago)

add support ReTweet?

Line 
1;;; twittering-mode.el --- Major mode for Twitter
2
3;; Copyright (C) 2007 Yuto Hayamizu.
4;;               2008 Tsuyoshi CHO
5
6;; Author: Y. Hayamizu <y.hayamizu@gmail.com>
7;;         Tsuyoshi CHO <Tsuyoshi.CHO+develop@Gmail.com>
8;; Created: Sep 4, 2007
9;; Version: 0.4
10;; Keywords: twitter web
11;; URL: http://lambdarepos.svnrepository.com/share/trac.cgi/browser/lang/elisp/twittering-mode
12
13;; Modified by Alberto Garcia <agarcia@igalia.com> to add the following methods:
14;; twittering-replies-timeline
15;; twittering-public-timeline
16;; twittering-user-timeline
17;; twittering-current-timeline(-(non)interractive)
18;; twittering-other-user-timeline
19;; twittering-other-user-timeline-interactive
20
21;; This file is free software; you can redistribute it and/or modify
22;; it under the terms of the GNU General Public License as published by
23;; the Free Software Foundation; either version 2, or (at your option)
24;; any later version.
25
26;; This file is distributed in the hope that it will be useful,
27;; but WITHOUT ANY WARRANTY; without even the implied warranty of
28;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29;; GNU General Public License for more details.
30
31;; You should have received a copy of the GNU General Public License
32;; along with GNU Emacs; see the file COPYING.  If not, write to
33;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
34;; Boston, MA 02111-1307, USA.
35
36;;; Commentary:
37
38;; twittering-mode.el is a major mode for Twitter.
39;; You can check friends timeline, and update your status on Emacs.
40
41;;; Feature Request:
42
43;; URL : http://twitter.com/d00dle/statuses/577876082
44;; URL : http://twitter.com/d00dle/statuses/577879732
45;; * Status Input from Popup buffer and C-cC-c to POST.
46;; * Mark fav(star)
47;; URL : http://code.nanigac.com/source/view/419
48;; * update status for region
49
50;;; Code:
51
52(require 'cl)
53(require 'xml)
54(require 'parse-time)
55
56(defconst twittering-mode-version "0.7")
57
58(defun twittering-mode-version ()
59  "Display a message for twittering-mode version."
60  (interactive)
61  (let ((version-string
62         (format "twittering-mode-v%s" twittering-mode-version)))
63    (if (interactive-p)
64        (message "%s" version-string)
65      version-string)))
66
67(defvar twittering-mode-map (make-sparse-keymap))
68
69(defvar twittering-timer nil "Timer object for timeline refreshing will be
70stored here. DO NOT SET VALUE MANUALLY.")
71
72(defvar twittering-idle-time 20)
73
74(defvar twittering-timer-interval 90)
75
76(defvar twittering-username nil)
77
78(defvar twittering-password nil)
79
80(defvar twittering-last-timeline-retrieved nil)
81
82(defvar twittering-last-timeline-interactive nil)
83
84(defvar twittering-new-tweets-count 0
85  "Number of new tweets when `twittering-new-tweets-hook' is run")
86
87(defvar twittering-new-tweets-hook nil
88  "Hook run when new twits are received.
89
90You can read `twittering-new-tweets-count' to get the number of new
91tweets received when this hook is run.")
92
93(defvar twittering-scroll-mode nil)
94(make-variable-buffer-local 'twittering-scroll-mode)
95
96(defvar twittering-jojo-mode nil)
97(make-variable-buffer-local 'twittering-jojo-mode)
98
99(defvar twittering-status-format nil)
100(setq twittering-status-format "%i %s,  %@:\n  %t // from %f%L%r")
101;; %s - screen_name
102;; %S - name
103;; %i - profile_image
104;; %d - description
105;; %l - location
106;; %L - " [location]"
107;; %r - " in reply to user"
108;; %u - url
109;; %j - user.id
110;; %p - protected?
111;; %c - created_at (raw UTC string)
112;; %C{time-format-str} - created_at (formatted with time-format-str)
113;; %@ - X seconds ago
114;; %t - text
115;; %' - truncated
116;; %f - source
117;; %# - id
118
119(defvar twittering-buffer "*twittering*")
120(defun twittering-buffer ()
121  (twittering-get-or-generate-buffer twittering-buffer))
122
123(defvar twittering-http-buffer "*twittering-http-buffer*")
124(defun twittering-http-buffer ()
125  (twittering-get-or-generate-buffer twittering-http-buffer))
126
127(defvar twittering-timeline-data nil)
128(defvar twittering-timeline-last-update nil)
129
130(defvar twittering-username-face 'twittering-username-face)
131(defvar twittering-uri-face 'twittering-uri-face)
132
133(defun twittering-get-or-generate-buffer (buffer)
134  (if (bufferp buffer)
135      (if (buffer-live-p buffer)
136          buffer
137        (generate-new-buffer (buffer-name buffer)))
138    (if (stringp buffer)
139        (or (get-buffer buffer)
140            (generate-new-buffer buffer)))))
141
142(defun assocref (item alist)
143  (cdr (assoc item alist)))
144(defmacro list-push (value listvar)
145  `(setq ,listvar (cons ,value ,listvar)))
146
147;;; Proxy
148(defvar twittering-proxy-use nil)
149(defvar twittering-proxy-server nil)
150(defvar twittering-proxy-port 8080)
151(defvar twittering-proxy-user nil)
152(defvar twittering-proxy-password nil)
153
154(defun twittering-toggle-proxy () ""
155  (interactive)
156  (setq twittering-proxy-use
157        (not twittering-proxy-use))
158  (message "%s %s"
159           "Use Proxy:"
160           (if twittering-proxy-use
161               "on" "off")))
162
163(defun twittering-user-agent-default-function ()
164  "Twittering mode default User-Agent function."
165  (concat "Emacs/"
166          (int-to-string emacs-major-version) "." (int-to-string
167                                                   emacs-minor-version)
168          " "
169          "Twittering-mode/"
170          twittering-mode-version))
171
172(defvar twittering-user-agent-function 'twittering-user-agent-default-function)
173
174(defun twittering-user-agent ()
175  "Return User-Agent header string."
176  (funcall twittering-user-agent-function))
177
178;;; to show image files
179
180(defvar twittering-wget-buffer "*twittering-wget-buffer*")
181(defun twittering-wget-buffer ()
182  (twittering-get-or-generate-buffer twittering-wget-buffer))
183
184(defvar twittering-tmp-dir
185  (expand-file-name (concat "twmode-images-" (user-login-name))
186                    temporary-file-directory))
187
188(defvar twittering-icon-mode nil "You MUST NOT CHANGE this variable
189directory. You should change through function'twittering-icon-mode'")
190
191(make-variable-buffer-local 'twittering-icon-mode)
192(defun twittering-icon-mode (&optional arg)
193  (interactive)
194  (setq twittering-icon-mode
195        (if twittering-icon-mode
196            (if (null arg)
197                nil
198              (> (prefix-numeric-value arg) 0))
199          (when (or (null arg)
200                    (and arg (> (prefix-numeric-value arg) 0)))
201            (when (file-writable-p twittering-tmp-dir)
202              (progn
203                (if (not (file-directory-p twittering-tmp-dir))
204                    (make-directory twittering-tmp-dir))
205                t)))))
206  (twittering-render-timeline))
207
208(defun twittering-scroll-mode (&optional arg)
209  (interactive)
210  (setq twittering-scroll-mode
211        (if (null arg)
212            (not twittering-scroll-mode)
213          (> (prefix-numeric-value arg) 0))))
214
215(defun twittering-jojo-mode (&optional arg)
216  (interactive)
217  (setq twittering-jojo-mode
218        (if (null arg)
219            (not twittering-jojo-mode)
220          (> (prefix-numeric-value arg) 0))))
221
222(defvar twittering-image-stack nil)
223
224(defun twittering-image-type (file-name)
225  (cond
226   ((string-match "\\.jpe?g" file-name) 'jpeg)
227   ((string-match "\\.png" file-name) 'png)
228   ((string-match "\\.gif" file-name) 'gif)
229   (t nil)))
230
231(defun twittering-setftime (fmt string uni)
232  (format-time-string fmt ; like "%Y-%m-%d %H:%M:%S"
233                      (apply 'encode-time (parse-time-string string))
234                      uni))
235(defun twittering-local-strftime (fmt string)
236  (twittering-setftime fmt string nil))
237(defun twittering-global-strftime (fmt string)
238  (twittering-setftime fmt string t))
239
240
241(defvar twittering-debug-mode nil)
242(defvar twittering-debug-buffer "*debug*")
243(defun twittering-debug-buffer ()
244  (twittering-get-or-generate-buffer twittering-debug-buffer))
245(defmacro debug-print (obj)
246  (let ((obsym (gensym)))
247    `(let ((,obsym ,obj))
248       (if twittering-debug-mode
249           (with-current-buffer (twittering-debug-buffer)
250             (insert (prin1-to-string ,obsym))
251             (newline)
252             ,obsym)
253         ,obsym))))
254
255(defun twittering-debug-mode ()
256  (interactive)
257  (setq twittering-debug-mode
258        (not twittering-debug-mode))
259  (message (if twittering-debug-mode "debug mode:on" "debug mode:off")))
260
261(if twittering-mode-map
262    (let ((km twittering-mode-map))
263      (define-key km "\C-c\C-f" 'twittering-friends-timeline)
264      (define-key km "\C-c\C-r" 'twittering-replies-timeline)
265      (define-key km "\C-c\C-g" 'twittering-public-timeline)
266      (define-key km "\C-c\C-u" 'twittering-user-timeline)
267      (define-key km "\C-c\C-s" 'twittering-update-status-interactive)
268      (define-key km "\C-c\C-e" 'twittering-erase-old-statuses)
269      (define-key km "\C-c\C-m" 'twittering-retweet)
270      (define-key km "\C-m" 'twittering-enter)
271      (define-key km "\C-c\C-l" 'twittering-update-lambda)
272      (define-key km [mouse-1] 'twittering-click)
273      (define-key km "\C-c\C-v" 'twittering-view-user-page)
274      (define-key km "g" 'twittering-current-timeline-interactive)
275      (define-key km "v" 'twittering-other-user-timeline)
276      (define-key km "V" 'twittering-other-user-timeline-interactive)
277      ;; (define-key km "j" 'next-line)
278      ;; (define-key km "k" 'previous-line)
279      (define-key km "j" 'twittering-goto-next-status)
280      (define-key km "k" 'twittering-goto-previous-status)
281      (define-key km "l" 'forward-char)
282      (define-key km "h" 'backward-char)
283      (define-key km "0" 'beginning-of-line)
284      (define-key km "^" 'beginning-of-line-text)
285      (define-key km "$" 'end-of-line)
286      (define-key km "n" 'twittering-goto-next-status-of-user)
287      (define-key km "p" 'twittering-goto-previous-status-of-user)
288      (define-key km [backspace] 'backward-char)
289      (define-key km "G" 'end-of-buffer)
290      (define-key km "H" 'beginning-of-buffer)
291      (define-key km "i" 'twittering-icon-mode)
292      (define-key km "s" 'twittering-scroll-mode)
293      (define-key km "t" 'twittering-toggle-proxy)
294      (define-key km "\C-c\C-p" 'twittering-toggle-proxy)
295      nil))
296
297(defvar twittering-mode-syntax-table nil "")
298
299(if twittering-mode-syntax-table
300    ()
301  (setq twittering-mode-syntax-table (make-syntax-table))
302  ;; (modify-syntax-entry ?  "" twittering-mode-syntax-table)
303  (modify-syntax-entry ?\" "w"  twittering-mode-syntax-table)
304  )
305
306(defun twittering-mode-init-variables ()
307  ;; (make-variable-buffer-local 'variable)
308  ;; (setq variable nil)
309  (font-lock-mode -1)
310  (defface twittering-username-face
311    `((t nil)) "" :group 'faces)
312  (copy-face 'font-lock-string-face 'twittering-username-face)
313  (set-face-attribute 'twittering-username-face nil :underline t)
314  (defface twittering-uri-face
315    `((t nil)) "" :group 'faces)
316  (set-face-attribute 'twittering-uri-face nil :underline t)
317  (add-to-list 'minor-mode-alist '(twittering-icon-mode " tw-icon"))
318  (add-to-list 'minor-mode-alist '(twittering-scroll-mode " tw-scroll"))
319  (add-to-list 'minor-mode-alist '(twittering-jojo-mode " tw-jojo"))
320  )
321
322(defmacro case-string (str &rest clauses)
323  `(cond
324    ,@(mapcar
325       (lambda (clause)
326         (let ((keylist (car clause))
327               (body (cdr clause)))
328           `(,(if (listp keylist)
329                  `(or ,@(mapcar (lambda (key) `(string-equal ,str ,key))
330                                 keylist))
331                't)
332             ,@body)))
333       clauses)))
334
335;; If you use Emacs21, decode-char 'ucs will fail unless Mule-UCS is loaded.
336;; TODO: Show error messages if Emacs 21 without Mule-UCS
337(defmacro twittering-ucs-to-char (num)
338  (if (functionp 'ucs-to-char)
339      `(ucs-to-char ,num)
340    `(decode-char 'ucs ,num)))
341
342(defvar twittering-mode-string "Twittering mode")
343
344(defvar twittering-mode-hook nil
345  "Twittering-mode hook.")
346
347(defun twittering-mode ()
348  "Major mode for Twitter
349\\{twittering-mode-map}"
350  (interactive)
351  (switch-to-buffer (twittering-buffer))
352  (kill-all-local-variables)
353  (twittering-mode-init-variables)
354  (use-local-map twittering-mode-map)
355  (setq major-mode 'twittering-mode)
356  (setq mode-name twittering-mode-string)
357  (set-syntax-table twittering-mode-syntax-table)
358  (run-hooks 'twittering-mode-hook)
359  (font-lock-mode -1)
360  (twittering-start))
361
362;;;
363;;; Basic HTTP functions
364;;;
365
366(defun twittering-http-get (method-class method &optional parameters sentinel)
367  (if (null sentinel) (setq sentinel 'twittering-http-get-default-sentinel))
368
369  ;; clear the buffer
370  (save-excursion
371    (set-buffer (twittering-http-buffer))
372    (erase-buffer))
373
374  (let (proc server port
375             (proxy-user twittering-proxy-user)
376             (proxy-password twittering-proxy-password))
377    (condition-case nil
378        (progn
379          (if (and twittering-proxy-use twittering-proxy-server)
380              (setq server twittering-proxy-server
381                    port (if (integerp twittering-proxy-port)
382                             (int-to-string twittering-proxy-port)
383                           twittering-proxy-port))
384            (setq server "twitter.com"
385                  port "80"))
386          (setq proc
387                (open-network-stream
388                 "network-connection-process" (twittering-http-buffer)
389                 server (string-to-number port)))
390          (set-process-sentinel proc sentinel)
391          (process-send-string
392           proc
393           (let ((nl "\r\n")
394                 request)
395             (setq request
396                   (concat "GET http://twitter.com/" method-class "/" method
397                           ".xml"
398                           (when parameters
399                             (concat "?"
400                                     (mapconcat
401                                      (lambda (param-pair)
402                                        (format "%s=%s"
403                                                (twittering-percent-encode (car
404                                                                            param-pair))
405                                                (twittering-percent-encode (cdr
406                                                                            param-pair))))
407                                      parameters
408                                      "&")))
409                           " HTTP/1.1" nl
410                           "Host: twitter.com" nl
411                           "User-Agent: " (twittering-user-agent) nl
412                           "Authorization: Basic "
413                           (base64-encode-string
414                            (concat twittering-username ":"
415                                    (twittering-get-password)))
416                           nl
417                           "Accept: text/xml"
418                           ",application/xml"
419                           ",application/xhtml+xml"
420                           ",application/html;q=0.9"
421                           ",text/plain;q=0.8"
422                           ",image/png,*/*;q=0.5" nl
423                           "Accept-Charset: utf-8;q=0.7,*;q=0.7" nl
424                           (when twittering-proxy-use
425                             "Proxy-Connection: Keep-Alive" nl
426                             (when (and proxy-user proxy-password)
427                               (concat
428                                "Proxy-Authorization: Basic "
429                                (base64-encode-string
430                                 (concat proxy-user ":"
431                                         proxy-password))
432                                nl)))
433                           nl))
434             (debug-print (concat "GET Request\n" request))
435             request)))
436      (error
437       (message "Failure: HTTP GET") nil))))
438
439(defun twittering-http-get-default-sentinel (proc stat &optional suc-msg)
440  (let ((header (twittering-get-response-header))
441        (body (twittering-get-response-body))
442        (status nil)
443        )
444    (if (string-match "HTTP/1\.[01] \\([a-z0-9 ]+\\)\r?\n" header)
445        (progn
446          (setq status (match-string-no-properties 1 header))
447          (case-string
448           status
449           (("200 OK")
450            (setq twittering-new-tweets-count
451                  (count t (mapcar
452                            #'twittering-cache-status-datum
453                            (reverse (twittering-xmltree-to-status
454                                      body)))))
455            (if (and (> twittering-new-tweets-count 0)
456                     (not twittering-last-timeline-interactive))
457                (run-hooks 'twittering-new-tweets-hook))
458            (setq twittering-last-timeline-interactive t)
459            (twittering-render-timeline)
460            (message (if suc-msg suc-msg "Success: Get.")))
461           (t (message status))))
462      (message "Failure: Bad http response.")))
463  )
464
465(defun twittering-render-timeline ()
466  (with-current-buffer (twittering-buffer)
467    (let ((point (point))
468          (end (point-max)))
469      (setq buffer-read-only nil)
470      (erase-buffer)
471      (mapc (lambda (status)
472              (insert (twittering-format-status
473                       status twittering-status-format))
474              (fill-region-as-paragraph
475               (save-excursion (beginning-of-line) (point)) (point))
476              (insert "\n"))
477            twittering-timeline-data)
478      (if (and twittering-image-stack window-system)
479          (clear-image-cache))
480      (setq buffer-read-only t)
481      (debug-print (current-buffer))
482      (goto-char (+ point (if twittering-scroll-mode (- (point-max) end) 0))))
483    ))
484
485(defun twittering-format-status (status format-str)
486  (flet ((attr (key)
487               (assocref key status))
488         (profile-image
489          ()
490          (let ((profile-image-url (attr 'user-profile-image-url))
491                (icon-string "\n  "))
492            (if (string-match "/\\([^/?]+\\)\\(?:\\?\\|$\\)" profile-image-url)
493                (let ((filename (match-string-no-properties 1
494                                                            profile-image-url)))
495                  ;; download icons if does not exist
496                  (if (file-exists-p (concat twittering-tmp-dir
497                                             "/" filename))
498                      t
499                    (add-to-list 'twittering-image-stack profile-image-url))
500
501                  (when (and icon-string twittering-icon-mode)
502                    (set-text-properties
503                     1 2 `(display
504                           (image :type ,(twittering-image-type filename)
505                                  :file ,(concat twittering-tmp-dir
506                                                 "/"
507                                                 filename)))
508                     icon-string)
509                    icon-string)
510                  )))))
511    (let ((cursor 0)
512          (result ())
513          c
514          found-at)
515      (setq cursor 0)
516      (setq result '())
517      (while (setq found-at (string-match "%\\(C{\\([^}]+\\)}\\|[A-Za-z#@']\\)"
518                                          format-str cursor))
519        (setq c (string-to-char (match-string-no-properties 1 format-str)))
520        (if (> found-at cursor)
521            (list-push (substring format-str cursor found-at) result)
522          "|")
523        (setq cursor (match-end 1))
524
525        (case c
526          ((?s)                         ; %s - screen_name
527           (list-push (attr 'user-screen-name) result))
528          ((?S)                         ; %S - name
529           (list-push (attr 'user-name) result))
530          ((?i)                         ; %i - profile_image
531           (list-push (profile-image) result))
532          ((?d)                         ; %d - description
533           (list-push (attr 'user-description) result))
534          ((?l)                         ; %l - location
535           (list-push (attr 'user-location) result))
536          ((?L)                         ; %L - " [location]"
537           (let ((location (attr 'user-location)))
538             (unless (or (null location) (string= "" location))
539               (list-push (concat " [" location "]") result)) ))
540          ((?u)                         ; %u - url
541           (list-push (attr 'user-url) result))
542          ((?j)                         ; %j - user.id
543           (list-push (attr 'user-id) result))
544          ((?r)                         ; %r - in_reply_to_status_id
545           (let ((reply-id (attr 'in-reply-to-status-id))
546                 (reply-name (attr 'in-reply-to-screen-name)))
547             (unless (or (null reply-id) (string= "" reply-id)
548                         (null reply-name) (string= "" reply-name))
549               (let ((in-reply-to-string (format "in reply to %s" reply-name))
550                     (url (twittering-get-status-url reply-name reply-id)))
551                 (add-text-properties
552                  0 (length in-reply-to-string)
553                  `(mouse-face highlight
554                               face twittering-uri-face
555                               uri ,url)
556                  in-reply-to-string)
557                 (list-push (concat " " in-reply-to-string) result)))))
558          ((?p)                         ; %p - protected?
559           (let ((protected (attr 'user-protected)))
560             (when (string= "true" protected)
561               (list-push "[x]" result))))
562          ((?c)                     ; %c - created_at (raw UTC string)
563           (list-push (attr 'created-at) result))
564          ((?C) ; %C{time-format-str} - created_at (formatted with
565           ; time-format-str)
566           (list-push (twittering-local-strftime
567                       (or (match-string-no-properties 2 format-str) "%H:%M:%S")
568                       (attr 'created-at))
569                      result))
570          ((?@)                         ; %@ - X seconds ago
571           (let ((created-at
572                  (apply
573                   'encode-time
574                   (parse-time-string (attr 'created-at))))
575                 (now (current-time)))
576             (let ((secs (+ (* (- (car now) (car created-at)) 65536)
577                            (- (cadr now) (cadr created-at))))
578                   time-string url)
579               (setq time-string
580                     (cond ((< secs 5) "less than 5 seconds ago")
581                           ((< secs 10) "less than 10 seconds ago")
582                           ((< secs 20) "less than 20 seconds ago")
583                           ((< secs 30) "half a minute ago")
584                           ((< secs 60) "less than a minute ago")
585                           ((< secs 150) "1 minute ago")
586                           ((< secs 2400) (format "%d minutes ago"
587                                                  (/ (+ secs 30) 60)))
588                           ((< secs 5400) "about 1 hour ago")
589                           ((< secs 84600) (format "about %d hours ago"
590                                                   (/ (+ secs 1800) 3600)))
591                           (t (format-time-string "%I:%M %p %B %d, %Y"
592                                                  created-at))))
593               (setq url (twittering-get-status-url (attr 'user-screen-name)
594                                                    (attr 'id)))
595               ;; make status url clickable
596               (add-text-properties
597                0 (length time-string)
598                `(mouse-face highlight
599                             face twittering-uri-face
600                             uri ,url)
601                time-string)
602               (list-push time-string result))))
603          ((?t)                         ; %t - text
604           (list-push                   ;(clickable-text)
605            (attr 'text)
606            result))
607          ((?')                         ; %' - truncated
608           (let ((truncated (attr 'truncated)))
609             (when (string= "true" truncated)
610               (list-push "..." result))))
611          ((?f)                         ; %f - source
612           (list-push (attr 'source) result))
613          ((?#)                         ; %# - id
614           (list-push (attr 'id) result))
615          (t
616           (list-push (char-to-string c) result)))
617        )
618      (list-push (substring format-str cursor) result)
619      (let ((formatted-status (apply 'concat (nreverse result))))
620        (add-text-properties 0 (length formatted-status)
621                             `(username ,(attr 'user-screen-name)
622                                        id ,(attr 'id)
623                                        text ,(attr 'text))
624                             formatted-status)
625        formatted-status)
626      )))
627
628(defun twittering-http-post
629  (method-class method &optional parameters contents sentinel)
630  "Send HTTP POST request to twitter.com
631
632METHOD-CLASS must be one of Twitter API method classes
633 (statuses, users or direct_messages).
634METHOD must be one of Twitter API method which belongs to METHOD-CLASS.
635PARAMETERS is alist of URI parameters.
636 ex) ((\"mode\" . \"view\") (\"page\" . \"6\")) => <URI>?mode=view&page=6"
637  (if (null sentinel) (setq sentinel 'twittering-http-post-default-sentinel))
638
639  ;; clear the buffer
640  (save-excursion
641    (set-buffer (twittering-http-buffer))
642    (erase-buffer))
643
644  (let (proc server port
645             (proxy-user twittering-proxy-user)
646             (proxy-password twittering-proxy-password))
647    (progn
648      (if (and twittering-proxy-use twittering-proxy-server)
649          (setq server twittering-proxy-server
650                port (if (integerp twittering-proxy-port)
651                         (int-to-string twittering-proxy-port)
652                       twittering-proxy-port))
653        (setq server "twitter.com"
654              port "80"))
655      (setq proc
656            (open-network-stream
657             "network-connection-process" (twittering-http-buffer)
658             server (string-to-number port)))
659      (set-process-sentinel proc sentinel)
660      (process-send-string
661       proc
662       (let ((nl "\r\n")
663             request)
664         (setq  request
665                (concat "POST http://twitter.com/" method-class "/" method ".xml"
666                        (when parameters
667                          (concat "?"
668                                  (mapconcat
669                                   (lambda (param-pair)
670                                     (format "%s=%s"
671                                             (twittering-percent-encode (car param-pair))
672                                             (twittering-percent-encode (cdr param-pair))))
673                                   parameters
674                                   "&")))
675                        " HTTP/1.1" nl
676                        "Host: twitter.com" nl
677                        "User-Agent: " (twittering-user-agent) nl
678                        "Authorization: Basic "
679                        (base64-encode-string
680                         (concat twittering-username ":" (twittering-get-password)))
681                        nl
682                        "Content-Type: text/plain" nl
683                        "Content-Length: 0" nl
684                        (when twittering-proxy-use
685                          "Proxy-Connection: Keep-Alive" nl
686                          (when (and proxy-user proxy-password)
687                            (concat
688                             "Proxy-Authorization: Basic "
689                             (base64-encode-string
690                              (concat proxy-user ":"
691                                      proxy-password))
692                             nl)))
693                        nl))
694         (debug-print (concat "POST Request\n" request))
695         request)))))
696
697(defun twittering-http-post-default-sentinel (proc stat &optional suc-msg)
698
699  (condition-case err-signal
700      (let ((header (twittering-get-response-header))
701            ;; (body (twittering-get-response-body)) not used now.
702            (status nil))
703        (string-match "HTTP/1\.1 \\([a-z0-9 ]+\\)\r?\n" header)
704        (setq status (match-string-no-properties 1 header))
705        (case-string status
706                     (("200 OK")
707                      (message (if suc-msg suc-msg "Success: Post")))
708                     (t (message status)))
709        )
710    (error (message (prin1-to-string err-signal))))
711  )
712
713(defun twittering-get-response-header (&optional buffer)
714  "Exract HTTP response header from HTTP response.
715`buffer' may be a buffer or the name of an existing buffer.
716 If `buffer' is omitted, the value of `twittering-http-buffer' is used as `buffer'."
717  (if (stringp buffer) (setq buffer (get-buffer buffer)))
718  (if (null buffer) (setq buffer (twittering-http-buffer)))
719  (save-excursion
720    (set-buffer buffer)
721    (let ((content (buffer-string)))
722      (substring content 0 (string-match "\r?\n\r?\n" content)))))
723
724(defun twittering-get-response-body (&optional buffer)
725  "Exract HTTP response body from HTTP response, parse it as XML, and return a
726XML tree as list. `buffer' may be a buffer or the name of an existing buffer. If
727`buffer' is omitted, the value of `twittering-http-buffer' is used as `buffer'."
728  (if (stringp buffer) (setq buffer (get-buffer buffer)))
729  (if (null buffer) (setq buffer (twittering-http-buffer)))
730  (save-excursion
731    (set-buffer buffer)
732    (let ((content (buffer-string)))
733      (let ((content (buffer-string)))
734        (xml-parse-region (+ (string-match "\r?\n\r?\n" content)
735                             (length (match-string 0 content)))
736                          (point-max)))
737      )))
738
739(defun twittering-cache-status-datum (status-datum &optional data-var)
740  "Cache status datum into data-var(default twittering-timeline-data)
741If STATUS-DATUM is already in DATA-VAR, return nil. If not, return t."
742  (if (null data-var)
743      (setf data-var 'twittering-timeline-data))
744  (let ((id (cdr (assq 'id status-datum))))
745    (if (or (null (symbol-value data-var))
746            (not (find-if
747                  (lambda (item)
748                    (string= id (cdr (assq 'id item))))
749                  (symbol-value data-var))))
750        (progn
751          (if twittering-jojo-mode
752              (twittering-update-jojo (cdr (assq 'user-screen-name
753                                                 status-datum))
754                                      (cdr (assq 'text status-datum))))
755          (set data-var (cons status-datum (symbol-value data-var)))
756          t)
757      nil)))
758
759(defun twittering-status-to-status-datum (status)
760  (flet ((assq-get (item seq)
761                   (car (cddr (assq item seq)))))
762    (let* ((status-data (cddr status))
763           id text source created-at truncated
764           in-reply-to-status-id
765           in-reply-to-screen-name
766           (user-data (cddr (assq 'user status-data)))
767           user-id user-name
768           user-screen-name
769           user-location
770           user-description
771           user-profile-image-url
772           user-url
773           user-protected
774           regex-index)
775
776      (setq id (assq-get 'id status-data))
777      (setq text (twittering-decode-html-entities
778                  (assq-get 'text status-data)))
779      (setq source (twittering-decode-html-entities
780                    (assq-get 'source status-data)))
781      (setq created-at (assq-get 'created_at status-data))
782      (setq truncated (assq-get 'truncated status-data))
783      (setq in-reply-to-status-id
784            (twittering-decode-html-entities
785             (assq-get 'in_reply_to_status_id status-data)))
786      (setq in-reply-to-screen-name
787            (twittering-decode-html-entities
788             (assq-get 'in_reply_to_screen_name status-data)))
789      (setq user-id (assq-get 'id user-data))
790      (setq user-name (twittering-decode-html-entities
791                       (assq-get 'name user-data)))
792      (setq user-screen-name (twittering-decode-html-entities
793                              (assq-get 'screen_name user-data)))
794      (setq user-location (twittering-decode-html-entities
795                           (assq-get 'location user-data)))
796      (setq user-description (twittering-decode-html-entities
797                              (assq-get 'description user-data)))
798      (setq user-profile-image-url (assq-get 'profile_image_url user-data))
799      (setq user-url (assq-get 'url user-data))
800      (setq user-protected (assq-get 'protected user-data))
801
802      ;; make username clickable
803      (add-text-properties
804       0 (length user-name)
805       `(mouse-face highlight
806                    uri ,(concat "http://twitter.com/" user-screen-name)
807                    face twittering-username-face)
808       user-name)
809
810      ;; make screen-name clickable
811      (add-text-properties
812       0 (length user-screen-name)
813       `(mouse-face highlight
814                    uri ,(concat "http://twitter.com/" user-screen-name)
815                    face twittering-username-face)
816       user-screen-name)
817
818      ;; make URI clickable
819      (setq regex-index 0)
820      (while regex-index
821        (setq regex-index
822              (string-match "@\\([_a-zA-Z0-9]+\\)\\|\\(https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+\\)"
823                            text
824                            regex-index))
825        (when regex-index
826          (let* ((matched-string (match-string-no-properties 0 text))
827                 (screen-name (match-string-no-properties 1 text))
828                 (uri (match-string-no-properties 2 text)))
829            (add-text-properties
830             (if screen-name
831                 (+ 1 (match-beginning 0))
832               (match-beginning 0))
833             (match-end 0)
834             (if screen-name
835                 `(mouse-face
836                   highlight
837                   face twittering-uri-face
838                   uri ,(concat "http://twitter.com/" screen-name))
839               `(mouse-face highlight
840                            face twittering-uri-face
841                            uri ,uri))
842             text))
843          (setq regex-index (match-end 0)) ))
844
845
846      ;; make source pretty and clickable
847      (if (string-match "<a href=\"\\(.*\\)\">\\(.*\\)</a>" source)
848          (let ((uri (match-string-no-properties 1 source))
849                (caption (match-string-no-properties 2 source)))
850            (setq source caption)
851            (add-text-properties
852             0 (length source)
853             `(mouse-face highlight
854                          uri ,uri
855                          face twittering-uri-face
856                          source ,source)
857             source)
858            ))
859
860      ;; save last update time
861      (setq twittering-timeline-last-update created-at)
862
863      (mapcar
864       (lambda (sym)
865         `(,sym . ,(symbol-value sym)))
866       '(id text source created-at truncated
867            in-reply-to-status-id
868            in-reply-to-screen-name
869            user-id user-name user-screen-name user-location
870            user-description
871            user-profile-image-url
872            user-url
873            user-protected)))))
874
875(defun twittering-xmltree-to-status (xmltree)
876  (mapcar #'twittering-status-to-status-datum
877          ;; quirk to treat difference between xml.el in Emacs21 and Emacs22
878          ;; On Emacs22, there may be blank strings
879          (let ((ret nil) (statuses (reverse (cddr (car xmltree)))))
880            (while statuses
881              (if (consp (car statuses))
882                  (setq ret (cons (car statuses) ret)))
883              (setq statuses (cdr statuses)))
884            ret)))
885
886(defun twittering-percent-encode (str &optional coding-system)
887  (if (or (null coding-system)
888          (not (coding-system-p coding-system)))
889      (setq coding-system 'utf-8))
890  (mapconcat
891   (lambda (c)
892     (cond
893      ((twittering-url-reserved-p c)
894       (char-to-string c))
895      ((eq c ? ) "+")
896      (t (format "%%%x" c))))
897   (encode-coding-string str coding-system)
898   ""))
899
900(defun twittering-url-reserved-p (ch)
901  (or (and (<= ?A ch) (<= ch ?z))
902      (and (<= ?0 ch) (<= ch ?9))
903      (eq ?. ch)
904      (eq ?- ch)
905      (eq ?_ ch)
906      (eq ?~ ch)))
907
908(defun twittering-decode-html-entities (encoded-str)
909  (if encoded-str
910      (let ((cursor 0)
911            (found-at nil)
912            (result '()))
913        (while (setq found-at
914                     (string-match "&\\(#\\([0-9]+\\)\\|\\([A-Za-z]+\\)\\);"
915                                   encoded-str cursor))
916          (when (> found-at cursor)
917            (list-push (substring encoded-str cursor found-at) result))
918          (let ((number-entity (match-string-no-properties 2 encoded-str))
919                (letter-entity (match-string-no-properties 3 encoded-str)))
920            (cond (number-entity
921                   (list-push
922                    (char-to-string
923                     (twittering-ucs-to-char
924                      (string-to-number number-entity))) result))
925                  (letter-entity
926                   (cond ((string= "gt" letter-entity) (list-push ">" result))
927                         ((string= "lt" letter-entity) (list-push "<" result))
928                         (t (list-push "?" result))))
929                  (t (list-push "?" result)))
930            (setq cursor (match-end 0))))
931        (list-push (substring encoded-str cursor) result)
932        (apply 'concat (nreverse result)))
933    ""))
934
935(defun twittering-timer-action (func)
936  (let ((buf (get-buffer twittering-buffer)))
937    (if (null buf)
938        (twittering-stop)
939      (funcall func)
940      )))
941
942(defun twittering-update-status-if-not-blank (status &optional reply-to-id)
943  (if (string-match "^\\s-*\\(?:@[-_a-z0-9]+\\)?\\s-*$" status)
944      nil
945    (let ((parameters (append `(("status" . ,status)
946                                ("source" . "twmode"))
947                              (if reply-to-id
948                                  `(("in_reply_to_status_id"
949                                     . ,reply-to-id))))))
950      (twittering-http-post "statuses" "update" parameters))
951    t))
952
953(defun twittering-update-status-from-minibuffer (&optional init-str
954                                                           reply-to-id)
955  (if (null init-str) (setq init-str ""))
956  (let ((status init-str) (not-posted-p t))
957    (while not-posted-p
958      (setq status (read-from-minibuffer "status: " status nil nil nil nil t))
959      (setq not-posted-p
960            (not (twittering-update-status-if-not-blank status reply-to-id))))
961    ))
962
963(defun twittering-update-lambda ()
964  (interactive)
965  (twittering-http-post
966   "statuses" "update"
967   `(("status" . "\xd34b\xd22b\xd26f\xd224\xd224\xd268\xd34b")
968     ("source" . "twmode"))))
969
970(defun twittering-update-jojo (usr msg)
971  (if (string-match "\xde21\xd24b\\(\xd22a\xe0b0\\|\xdaae\xe6cd\\)\xd24f\xd0d6\\([^\xd0d7]+\\)\xd0d7\xd248\xdc40\xd226"
972                    msg)
973      (twittering-http-post
974       "statuses" "update"
975       `(("status" . ,(concat
976                       "@" usr " "
977                       (match-string-no-properties 2 msg)
978                       "\xd0a1\xd24f\xd243!?"))
979         ("source" . "twmode")))))
980
981;;;
982;;; Commands
983;;;
984
985(defun twittering-start (&optional action)
986  (interactive)
987  (if (null action)
988      (setq action #'twittering-current-timeline-noninteractive))
989  (if twittering-timer
990      nil
991    (setq twittering-timer
992          (run-at-time "0 sec"
993                       twittering-timer-interval
994                       #'twittering-timer-action action))))
995
996(defun twittering-stop ()
997  (interactive)
998  (cancel-timer twittering-timer)
999  (setq twittering-timer nil))
1000
1001(defun twittering-get-timeline (method)
1002  (if (not (eq twittering-last-timeline-retrieved method))
1003      (setq twittering-timeline-last-update nil
1004            twittering-timeline-data nil))
1005  (setq twittering-last-timeline-retrieved method)
1006  (let ((buf (get-buffer twittering-buffer)))
1007    (if (not buf)
1008        (twittering-stop)
1009      (if (not twittering-timeline-last-update)
1010          (twittering-http-get "statuses" method)
1011        (let* ((system-time-locale "C")
1012               (since
1013                (twittering-global-strftime
1014                 "%a, %d %b %Y %H:%M:%S GMT"
1015                 twittering-timeline-last-update)))
1016          (twittering-http-get "statuses" method
1017                               `(("since" . ,since)))))))
1018
1019  (if (and twittering-icon-mode window-system)
1020      (if twittering-image-stack
1021          (let ((proc
1022                 (apply
1023                  #'start-process
1024                  "wget-images"
1025                  (twittering-wget-buffer)
1026                  "wget"
1027                  (format "--directory-prefix=%s" twittering-tmp-dir)
1028                  "--no-clobber"
1029                  "--quiet"
1030                  twittering-image-stack)))
1031            (set-process-sentinel
1032             proc
1033             (lambda (proc stat)
1034               (clear-image-cache)
1035               (save-excursion
1036                 (set-buffer (twittering-wget-buffer))
1037                 )))))))
1038
1039(defun twittering-friends-timeline ()
1040  (interactive)
1041  (twittering-get-timeline "friends_timeline"))
1042
1043(defun twittering-replies-timeline ()
1044  (interactive)
1045  (twittering-get-timeline "replies"))
1046
1047(defun twittering-public-timeline ()
1048  (interactive)
1049  (twittering-get-timeline "public_timeline"))
1050
1051(defun twittering-user-timeline ()
1052  (interactive)
1053  (twittering-get-timeline "user_timeline"))
1054
1055(defun twittering-current-timeline-interactive ()
1056  (interactive)
1057  (setq twittering-last-timeline-interactive t)
1058  (twittering-current-timeline))
1059
1060(defun twittering-current-timeline-noninteractive ()
1061  (setq twittering-last-timeline-interactive nil)
1062  (twittering-current-timeline))
1063
1064(defun twittering-current-timeline ()
1065  (if (not twittering-last-timeline-retrieved)
1066      (setq twittering-last-timeline-retrieved "friends_timeline"))
1067  (twittering-get-timeline twittering-last-timeline-retrieved))
1068
1069(defun twittering-update-status-interactive ()
1070  (interactive)
1071  (twittering-update-status-from-minibuffer))
1072
1073(defun twittering-erase-old-statuses ()
1074  (interactive)
1075  (setq twittering-timeline-data nil)
1076  (if (not twittering-last-timeline-retrieved)
1077      (setq twittering-last-timeline-retrieved "friends_timeline"))
1078  (if (not twittering-timeline-last-update)
1079      (twittering-http-get "statuses" twittering-last-timeline-retrieved)
1080    (let* ((system-time-locale "C")
1081           (since
1082            (twittering-global-strftime
1083             "%a, %d %b %Y %H:%M:%S GMT"
1084             twittering-timeline-last-update)))
1085      (twittering-http-get "statuses" twittering-last-timeline-retrieved
1086                           `(("since" . ,since))))))
1087
1088(defun twittering-click ()
1089  (interactive)
1090  (let ((uri (get-text-property (point) 'uri)))
1091    (if uri
1092        (browse-url uri))))
1093
1094(defun twittering-enter ()
1095  (interactive)
1096  (let ((username (get-text-property (point) 'username))
1097        (id (get-text-property (point) 'id))
1098        (uri (get-text-property (point) 'uri)))
1099    (if username
1100        (twittering-update-status-from-minibuffer (concat "@" username " ") id)
1101      (if uri
1102          (browse-url uri)))))
1103
1104(defun twittering-retweet ()
1105  (interactive)
1106  (let ((username (get-text-property (point) 'username))
1107        (id (get-text-property (point) 'id))
1108        (text (get-text-property (point) 'text)))
1109    (when username
1110        (twittering-update-status-from-minibuffer
1111         (concat "RT: " text " (via @" username ")") id))))
1112
1113(defun twittering-view-user-page ()
1114  (interactive)
1115  (let ((uri (get-text-property (point) 'uri)))
1116    (if uri
1117        (browse-url uri))))
1118
1119(defun twittering-other-user-timeline ()
1120  (interactive)
1121  (let ((username (get-text-property (point) 'username)))
1122    (if (> (length username) 0)
1123        (twittering-get-timeline (concat "user_timeline/" username))
1124      (message "No user selected"))))
1125
1126(defun twittering-other-user-timeline-interactive ()
1127  (interactive)
1128  (let ((username (read-from-minibuffer "user: " (get-text-property (point) 'username))))
1129    (if (> (length username) 0)
1130        (twittering-get-timeline (concat "user_timeline/" username))
1131      (message "No user selected"))))
1132
1133(defun twittering-reply-to-user ()
1134  (interactive)
1135  (let ((username (get-text-property (point) 'username)))
1136    (if username
1137        (twittering-update-status-from-minibuffer (concat "@" username " ")))))
1138
1139(defun twittering-get-password ()
1140  (or twittering-password
1141      (setq twittering-password (read-passwd "twittering-mode: "))))
1142
1143(defun twittering-goto-next-status ()
1144  "Go to next status."
1145  (interactive)
1146  (let ((pos))
1147    (setq pos (twittering-get-next-username-face-pos (point)))
1148    (if pos
1149        (goto-char pos)
1150      (message "End of status."))))
1151
1152(defun twittering-get-next-username-face-pos (pos)
1153  (interactive)
1154  (let ((prop))
1155    (catch 'not-found
1156      (while (and pos (not (eq prop twittering-username-face)))
1157        (setq pos (next-single-property-change pos 'face))
1158        (when (eq pos nil) (throw 'not-found nil))
1159        (setq prop (get-text-property pos 'face)))
1160      pos)))
1161
1162(defun twittering-goto-previous-status ()
1163  "Go to previous status."
1164  (interactive)
1165  (let ((pos))
1166    (setq pos (twittering-get-previous-username-face-pos (point)))
1167    (if pos
1168        (goto-char pos)
1169      (message "Start of status."))))
1170
1171(defun twittering-get-previous-username-face-pos (pos)
1172  (interactive)
1173  (let ((prop))
1174    (catch 'not-found
1175      (while (and pos (not (eq prop twittering-username-face)))
1176        (setq pos (previous-single-property-change pos 'face))
1177        (when (eq pos nil) (throw 'not-found nil))
1178        (setq prop (get-text-property pos 'face)))
1179      pos)))
1180
1181(defun twittering-goto-next-status-of-user ()
1182  "Go to next status of user."
1183  (interactive)
1184  (let ((user-name (twittering-get-username-at-pos (point)))
1185        (pos (twittering-get-next-username-face-pos (point))))
1186    (while (and (not (eq pos nil))
1187                (not (equal (twittering-get-username-at-pos pos) user-name)))
1188      (setq pos (twittering-get-next-username-face-pos pos)))
1189    (if pos
1190        (goto-char pos)
1191      (if user-name
1192          (message "End of %s's status." user-name)
1193        (message "Invalid user-name.")))))
1194
1195(defun twittering-goto-previous-status-of-user ()
1196  "Go to previous status of user."
1197  (interactive)
1198  (let ((user-name (twittering-get-username-at-pos (point)))
1199        (pos (twittering-get-previous-username-face-pos (point))))
1200    (while (and (not (eq pos nil))
1201                (not (equal (twittering-get-username-at-pos pos) user-name)))
1202      (setq pos (twittering-get-previous-username-face-pos pos)))
1203    (if pos
1204        (goto-char pos)
1205      (if user-name
1206          (message "Start of %s's status." user-name)
1207        (message "Invalid user-name.")))))
1208
1209(defun twittering-get-username-at-pos (pos)
1210  (let ((start-pos pos)
1211        (end-pos))
1212    (catch 'not-found
1213      (while (eq (get-text-property start-pos 'face) twittering-username-face)
1214        (setq start-pos (1- start-pos))
1215        (when (or (eq start-pos nil) (eq start-pos 0)) (throw 'not-found nil)))
1216      (setq start-pos (1+ start-pos))
1217      (setq end-pos (next-single-property-change pos 'face))
1218      (buffer-substring start-pos end-pos))))
1219
1220(defun twittering-get-status-url (username id)
1221  "Generate status URL."
1222  (format "http://twitter.com/%s/statuses/%s" username id))
1223
1224;;;###autoload
1225(defun twit ()
1226  "Start twittering-mode."
1227  (interactive)
1228  (twittering-mode))
1229
1230(provide 'twittering-mode)
1231;;; twittering.el ends here
Note: See TracBrowser for help on using the browser.