root/lang/elisp/wassr-mode/trunk/wassr-mode.el @ 60

Revision 60, 30.7 kB (checked in by tsuyoshi, 16 years ago)

Version 0.3

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