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

Revision 138, 39.4 kB (checked in by tsuyoshi, 16 years ago)

support sign and some fix. coming new Author Alberto.

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