ed009966fed87252ff28789ff7603fd5f4a60b49
[elisp/mixi.git] / mixi.el
1 ;; mixi.el --- API library for accessing to mixi
2
3 ;; Copyright (C) 2005,2006 OHASHI Akira
4
5 ;; Author: OHASHI Akira <bg66@koka-in.org>
6 ;; Keywords: hypermedia
7
8 ;; This file is *NOT* a part of Emacs.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program; if not, you can either send email to this
22 ;; program's maintainer or write to: The Free Software Foundation,
23 ;; Inc.; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24
25 ;;; Commentary:
26
27 ;; API for getting contents:
28 ;;
29 ;;  * mixi-get-friends
30 ;;  * mixi-get-favorites
31 ;;  * mixi-get-logs
32 ;;  * mixi-get-diaries
33 ;;  * mixi-get-new-diaries
34 ;;  * mixi-search-diaries
35 ;;  * mixi-get-communities
36 ;;  * mixi-search-communities
37 ;;  * mixi-get-bbses
38 ;;  * mixi-get-new-bbses
39 ;;  * mixi-search-bbses
40 ;;  * mixi-get-comments
41 ;;  * mixi-get-new-comments
42 ;;  * mixi-get-messages
43 ;;  * mixi-get-introductions
44 ;;  * mixi-get-news
45 ;;
46 ;; API for posting:
47 ;;
48 ;;  * mixi-post-diary
49 ;;  * mixi-post-topic
50 ;;  * mixi-post-comment
51 ;;  * mixi-post-message
52 ;; 
53 ;; Utilities:
54 ;;
55 ;;  * mixi-remove-markup
56
57 ;; Example:
58 ;;
59 ;; Display newest 3 diaries like a mail format.
60 ;;
61 ;; (let ((range 3)
62 ;;       (buffer (get-buffer-create "*temp*"))
63 ;;       (format "%Y/%m/%d %H:%M"))
64 ;;   (pop-to-buffer buffer)
65 ;;   (mapc (lambda (diary)
66 ;;        (let ((subject (mixi-diary-title diary))
67 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
68 ;;              (date (format-time-string format (mixi-diary-time diary)))
69 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
70 ;;          (insert "From: " from "\n"
71 ;;                  "Subject: " subject "\n"
72 ;;                  "Date: " date "\n\n"
73 ;;                  body "\n\n")))
74 ;;      (mixi-get-new-diaries range))
75 ;;   (set-buffer-modified-p nil)
76 ;;   (setq buffer-read-only t)
77 ;;   (goto-char (point-min)))
78 ;;
79 ;; Display newest 3 diaries including newest 3 comments like a mail format.
80 ;; Comments are displayed like a reply mail.
81 ;;
82 ;; (let ((range 3)
83 ;;       (buffer (get-buffer-create "*temp*"))
84 ;;       (format "%Y/%m/%d %H:%M"))
85 ;;   (pop-to-buffer buffer)
86 ;;   (mapc (lambda (diary)
87 ;;        (let ((subject (mixi-diary-title diary))
88 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
89 ;;              (date (format-time-string format (mixi-diary-time diary)))
90 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
91 ;;          (insert "From: " from "\n"
92 ;;                  "Subject: " subject "\n"
93 ;;                  "Date: " date "\n\n"
94 ;;                  body "\n\n")
95 ;;          (mapc (lambda (comment)
96 ;;                  (let ((from (mixi-friend-nick
97 ;;                               (mixi-comment-owner comment)))
98 ;;                        (subject (concat "Re: " subject))
99 ;;                        (date (format-time-string
100 ;;                               format (mixi-comment-time comment)))
101 ;;                        (body (mixi-remove-markup
102 ;;                               (mixi-comment-content comment))))
103 ;;                    (insert "From: " from "\n"
104 ;;                            "Subject: " subject "\n"
105 ;;                            "Date: " date "\n\n"
106 ;;                            body "\n\n")))
107 ;;                (mixi-get-comments diary range))))
108 ;;      (mixi-get-new-diaries range))
109 ;;   (set-buffer-modified-p nil)
110 ;;   (setq buffer-read-only t)
111 ;;   (goto-char (point-min)))
112
113 ;; Bug reports:
114 ;;
115 ;; If you have bug reports and/or suggestions for improvement, please
116 ;; send them via <URL:http://mixi.jp/view_community.pl?id=1596390>.
117
118 ;;; Code:
119
120 (eval-when-compile (require 'cl))
121
122 ;; Functions and variables which should be defined in the other module
123 ;; at run-time.
124 (eval-when-compile
125   (defvar w3m-use-cookies)
126   (autoload 'w3m-decode-buffer "w3m")
127   (autoload 'w3m-retrieve "w3m"))
128
129 (defgroup mixi nil
130   "API library for accessing to mixi."
131   :group 'hypermedia)
132
133 (defcustom mixi-url "http://mixi.jp"
134   "*The URL of mixi."
135   :type 'string
136   :group 'mixi)
137
138 (defcustom mixi-directory (expand-file-name "~/.mixi")
139   "*Where to look for mixi files."
140   :type 'directory
141   :group 'mixi)
142
143 (defcustom mixi-coding-system 'euc-jp
144   "*Coding system for mixi."
145   :type 'coding-system
146   :group 'mixi)
147
148 (defcustom mixi-curl-program "curl"
149   "*The program name of `curl'."
150   :type 'file
151   :group 'mixi)
152
153 (defcustom mixi-curl-cookie-file (expand-file-name "cookies.txt"
154                                                    mixi-directory)
155   "*The location of cookie file created by `curl'."
156   :type 'file
157   :group 'mixi)
158
159 (defcustom mixi-backend
160   (or (condition-case nil
161           (progn
162             (require 'w3m)
163             'w3m)
164         (error))
165       (condition-case nil
166           (progn
167             (require 'url)
168             (if (fboundp 'url-retrieve-synchronously)
169                 'url))
170         (error))
171       (if (and (fboundp 'executable-find)
172                (executable-find mixi-curl-program))
173           'curl)
174       (error "Cannot set `mixi-backend'."))
175   "*The backend for accessing to mixi."
176   :type '(radio (const :tag "Use emacs-w3m" w3m)
177                 (const :tag "Use url.el" url)
178                 (const :tag "Use curl" curl)
179                 (symbol :tag "The other backend"))
180   :group 'mixi)
181
182 (defcustom mixi-login-use-ssl nil
183   "*If non-ni, login using SSL."
184   :type 'boolean
185   :group 'mixi)
186
187 (defcustom mixi-default-email nil
188   "*Default E-mail address that is used to login automatically."
189   :type '(radio (string :tag "E-mail address")
190                 (const :tag "Asked when it is necessary" nil))
191   :group 'mixi)
192
193 (defcustom mixi-default-password nil
194   "*Default password that is used to login automatically."
195   :type '(radio (string :tag "Password")
196                 (const :tag "Asked when it is necessary" nil))
197   :group 'mixi)
198
199 (defcustom mixi-accept-adult-contents t
200   "*If non-nil, accept to access to the adult contents."
201   :type 'boolean
202   :group 'mixi)
203
204 (defcustom mixi-continuously-access-interval 4.0
205   "*Time interval between each mixi access.
206 Increase this value when unexpected error frequently occurs."
207   :type 'number
208   :group 'mixi)
209
210 (defcustom mixi-cache-expires nil
211   "*Seconds for expiration of a cached object."
212   :type '(radio (integer :tag "Expired seconds")
213                 (const :tag "Don't expire" nil)
214                 (const :tag "Don't cache" t))
215   :group 'mixi)
216
217 ;; FIXME: Not implemented.
218 (defcustom mixi-cache-use-file t
219   "*If non-nil, caches are saved to files."
220   :type 'boolean
221   :group 'mixi)
222
223 (defvar mixi-me nil)
224
225 ;; Utilities.
226 (defmacro mixi-message (&rest strings)
227   `(concat "[mixi] " ,@strings))
228
229 (put 'mixi-realization-error
230      'error-message (mixi-message "Cannot realize object"))
231 (put 'mixi-realization-error
232      'error-conditions '(mixi-realization-error error))
233
234 (put 'mixi-post-error
235      'error-message (mixi-message "Cannot post"))
236 (put 'mixi-post-error
237      'error-conditions '(mixi-post-error error))
238
239 (defmacro mixi-realization-error (type object)
240   `(let ((data (if debug-on-error
241                    (list ,type ,object (buffer-string))
242                  (list ,type ,object))))
243      (signal 'mixi-realization-error data)))
244
245 (defmacro mixi-post-error (type &optional object)
246   `(let ((data (when debug-on-error (list (buffer-string)))))
247      (if ,object
248          (setq data (cons ,type (cons ,object data)))
249        (setq data (cons ,type data)))
250      (signal 'mixi-post-error data)))
251
252 (defconst mixi-message-adult-contents
253   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
254 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
255 (defconst mixi-message-continuously-accessing
256   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
257 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
258 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
259 (defconst mixi-warning-continuously-accessing
260   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
261 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
262 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
263
264 (defmacro mixi-retrieve (url &optional post-data)
265   `(funcall (intern (concat "mixi-" (symbol-name mixi-backend) "-retrieve"))
266             ,url ,post-data))
267
268 (defmacro mixi-post-form (url fields)
269   `(funcall (intern (concat "mixi-" (symbol-name mixi-backend) "-post-form"))
270             ,url ,fields))
271
272 (defun mixi-parse-buffer (url buffer &optional post-data)
273   (when (string-match mixi-message-adult-contents buffer)
274     (if mixi-accept-adult-contents
275         (setq buffer (mixi-retrieve url "submit=agree"))
276       (setq buffer (mixi-retrieve (concat url "?")))))
277   (when (string-match mixi-warning-continuously-accessing buffer)
278     (error (mixi-message "Access denied.  Please wait a while and increase "
279                          "the value of `mixi-continuously-access-interval'.")))
280   (if (not (string-match mixi-message-continuously-accessing buffer))
281       buffer
282     (message (mixi-message "Waiting for continuously accessing..."))
283     (sleep-for mixi-continuously-access-interval)
284     (mixi-retrieve url post-data)))
285
286 (defmacro mixi-expand-url (url)
287   `(if (string-match "^http" ,url)
288        ,url
289      (concat mixi-url ,url)))
290
291 ;; FIXME: Support file, checkbox and so on.
292 (defun mixi-make-form-data (fields)
293   "Make form data and return (CONTENT-TYPE . FORM-DATA)."
294   (let* ((boundary (apply 'format "--_%d_%d_%d" (current-time)))
295          (content-type (concat "multipart/form-data; boundary=" boundary))
296          (form-data
297           (mapconcat
298            (lambda (field)
299              (concat "--" boundary "\r\n"
300                      "Content-Disposition: form-data; name=\""
301                      (car field) "\"\r\n"
302                      "\r\n"
303                      (encode-coding-string (cdr field) mixi-coding-system)))
304            fields "\r\n")))
305     (cons content-type (concat form-data "\r\n--" boundary "--"))))
306
307 (defun mixi-url-retrieve (url &optional post-data extra-headers)
308   "Retrieve the URL and return gotten strings."
309   (let* ((url-request-method (if post-data "POST" "GET"))
310          (url-request-data post-data)
311          (url-request-extra-headers extra-headers)
312          (url (mixi-expand-url url))
313          (buffer (url-retrieve-synchronously url))
314          ret)
315     (unless (bufferp buffer)
316       (error (mixi-message "Cannot retrieve")))
317     (with-current-buffer buffer
318       (goto-char (point-min))
319       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
320         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
321       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
322         (error (mixi-message "Cannot retrieve")))
323       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
324       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
325       (kill-buffer buffer)
326       (mixi-parse-buffer url ret post-data))))
327
328 (defun mixi-url-post-form (url fields)
329   (let* ((form-data (mixi-make-form-data fields))
330          (extra-headers `(("Content-Type" . ,(car form-data)))))
331     (mixi-url-retrieve url (cdr form-data) extra-headers)))
332
333 (defun mixi-w3m-retrieve (url &optional post-data)
334   "Retrieve the URL and return gotten strings."
335   (let ((url (mixi-expand-url url)))
336     (with-temp-buffer
337       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
338           (error (mixi-message "Cannot retrieve"))
339         (w3m-decode-buffer url)
340         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
341           (mixi-parse-buffer url ret post-data))))))
342
343 (defun mixi-w3m-post-form (url fields)
344   (let ((form-data (mixi-make-form-data fields)))
345     (mixi-w3m-retrieve url form-data)))
346
347 (defun mixi-curl-retrieve (url &optional post-data)
348   "Retrieve the URL and return gotten strings."
349   (with-temp-buffer
350     (if (fboundp 'set-buffer-multibyte)
351         (set-buffer-multibyte nil))
352     (let ((orig-mode (default-file-modes))
353           (coding-system-for-read 'binary)
354           (coding-system-for-write 'binary)
355           process ret)
356       (unwind-protect
357           (progn
358             (set-default-file-modes 448)
359             (setq process
360                   (apply #'start-process "curl" (current-buffer)
361                          mixi-curl-program
362                          (append (if post-data '("-d" "@-"))
363                                  (list "-i" "-L" "-s"
364                                        "-b" mixi-curl-cookie-file
365                                        "-c" mixi-curl-cookie-file
366                                        (mixi-expand-url url)))))
367             (set-process-sentinel process #'ignore))
368         (set-default-file-modes orig-mode))
369       (when post-data
370         (process-send-string process (concat post-data "\n"))
371         (process-send-eof process))
372       (while (eq (process-status process) 'run)
373         (accept-process-output process 1))
374       (goto-char (point-min))
375       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
376         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
377       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
378         (error (mixi-message "Cannot retrieve")))
379       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
380       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
381       (mixi-parse-buffer url ret post-data))))
382
383 (defconst mixi-my-id-regexp
384   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
385
386 (defun mixi-login (&optional email password)
387   "Login to mixi."
388   (when (and (eq mixi-backend 'w3m) (not w3m-use-cookies))
389     (error (mixi-message "Require to accept cookies.  Please set "
390                          "`w3m-use-cookies' to t.")))
391   (let ((email (or email mixi-default-email
392                    (read-from-minibuffer (mixi-message "Login Email: "))))
393         (password (or password mixi-default-password
394                       (read-passwd (mixi-message "Login Password: ")))))
395     (let ((url "/login.pl"))
396       (when mixi-login-use-ssl
397         (setq url (concat "https://mixi.jp" url)))
398       (let ((buffer (mixi-retrieve url
399                                    (concat "email=" email
400                                            "&password=" password
401                                            "&next_url=/home.pl"
402                                            "&sticky=on"))))
403         (unless (string-match "url=/check\\.pl\\?n=" buffer)
404           (error (mixi-message "Cannot login")))
405         (setq buffer (mixi-retrieve "/check.pl?n=home.pl"))
406         (if (string-match mixi-my-id-regexp buffer)
407             (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
408           (error (mixi-message "Cannot login")))))))
409
410 (defun mixi-logout ()
411   (mixi-retrieve "/logout.pl"))
412
413 (defmacro with-mixi-retrieve (url &rest body)
414   `(when ,url
415      (with-temp-buffer
416        (insert (mixi-retrieve ,url))
417        (goto-char (point-min))
418        (when (search-forward
419               "<form action=\"login.pl\" method=\"post\">" nil t)
420          (mixi-login)
421          (erase-buffer)
422          (insert (mixi-retrieve ,url))
423          (goto-char (point-min)))
424        ,@body)))
425 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
426 (put 'with-mixi-retrieve 'edebug-form-spec '(body))
427
428 (defmacro with-mixi-post-form (url fields &rest body)
429   `(when ,url
430      (with-temp-buffer
431        (insert (mixi-post-form ,url ,fields))
432        (goto-char (point-min))
433        (when (search-forward
434               "<form action=\"login.pl\" method=\"post\">" nil t)
435          (mixi-login)
436          (erase-buffer)
437          (insert (mixi-post-form ,url ,fields))
438          (goto-char (point-min)))
439        ,@body)))
440 (put 'with-mixi-post-form 'lisp-indent-function 'defun)
441 (put 'with-mixi-post-form 'edebug-form-spec '(body))
442
443 (defun mixi-get-matched-items (url regexp &optional range)
444   "Get matched items to REGEXP in URL."
445   (let ((page 1)
446         ids)
447     (catch 'end
448       (while (or (null range) (< (length ids) range))
449         (with-mixi-retrieve (format url page)
450           (let (found)
451             (while (and (re-search-forward regexp nil t)
452                         (or (null range) (< (length ids) range)))
453               (let ((num 1)
454                     list)
455                 (while (match-string num)
456                   (setq list (cons (match-string num) list))
457                   (incf num))
458                 (when (not (member (reverse list) ids))
459                   (setq found t)
460                   (setq ids (cons (reverse list) ids)))))
461             (when (not found)
462               (throw 'end ids))))
463         (incf page)))
464     (reverse ids)))
465
466 ;; stolen (and modified) from shimbun.el
467 (defun mixi-remove-markup (string)
468   "Remove markups from STRING."
469   (with-temp-buffer
470     (insert (or string ""))
471     (save-excursion
472       (goto-char (point-min))
473       (while (search-forward "<!--" nil t)
474         (delete-region (match-beginning 0)
475                        (or (search-forward "-->" nil t)
476                            (point-max))))
477       (goto-char (point-min))
478       (while (re-search-forward "<[^>]+>" nil t)
479         (replace-match "" t t))
480       (goto-char (point-min))
481       (while (re-search-forward "\r" nil t)
482         (replace-match "\n" t t)))
483     ;; FIXME: Decode entities.
484     (buffer-string)))
485
486 ;; stolen (and modified) from w3m.el
487 ;; FIXME: Hmm.
488 (defun mixi-url-encode-and-quote-percent-string (string)
489   (apply (function concat)
490          (mapcar
491           (lambda (char)
492             (cond
493              ((eq char ?\n)             ; newline
494               "%%0D%%0A")
495              ((string-match "[-a-zA-Z0-9_:/.]" (char-to-string char)) ; xxx?
496               (char-to-string char))    ; printable
497              ((char-equal char ?\x20)   ; space
498               "+")
499              (t
500               (format "%%%%%02x" char))))       ; escape
501           ;; Coerce a string into a list of chars.
502           (append (encode-coding-string (or string "") mixi-coding-system)
503                   nil))))
504
505 ;; Object.
506 (defconst mixi-object-prefix "mixi-")
507
508 (defmacro mixi-object-class (object)
509   `(car-safe ,object))
510
511 (defmacro mixi-object-p (object)
512   `(let ((class (mixi-object-class ,object)))
513      (when (symbolp class)
514        (eq (string-match (concat "^" mixi-object-prefix)
515                          (symbol-name class)) 0))))
516
517 (defun mixi-object-name (object)
518   "Return the name of OBJECT."
519   (unless (mixi-object-p object)
520     (signal 'wrong-type-argument (list 'mixi-object-p object)))
521   (let ((class (mixi-object-class object)))
522     (substring (symbol-name class) (length mixi-object-prefix))))
523
524 (defun mixi-read-object (exp)
525   "Read one Lisp expression as mixi object."
526   (if (mixi-object-p exp)
527       (let ((func (intern (concat mixi-object-prefix "make-"
528                                   (mixi-object-name exp))))
529             (args (mapcar (lambda (arg)
530                             (mixi-read-object arg))
531                           (cdr exp))))
532         (let ((object (apply func (cdr args))))
533           (when (car args)
534             (mixi-object-set-timestamp object (car args)))
535           object))
536     exp))
537
538 (defun mixi-object-timestamp (object)
539   "Return the timestamp of OJBECT."
540   (unless (mixi-object-p object)
541     (signal 'wrong-type-argument (list 'mixi-object-p object)))
542   (aref (cdr object) 0))
543 (defalias 'mixi-object-realized-p 'mixi-object-timestamp)
544
545 (defun mixi-object-owner (object)
546   "Return the owner of OBJECT."
547   (unless (mixi-object-p object)
548     (signal 'wrong-type-argument (list 'mixi-object-p object)))
549   (let ((func (intern (concat mixi-object-prefix
550                               (mixi-object-name object) "-owner"))))
551     (funcall func object)))
552
553 (defun mixi-object-id (object)
554   "Return the id of OBJECT."
555   (unless (mixi-object-p object)
556     (signal 'wrong-type-argument (list 'mixi-object-p object)))
557   (let ((func (intern (concat mixi-object-prefix
558                               (mixi-object-name object) "-id"))))
559     (funcall func object)))
560
561 (defun mixi-object-time (object)
562   "Return the time of OBJECT."
563   (unless (mixi-object-p object)
564     (signal 'wrong-type-argument (list 'mixi-object-p object)))
565   (let ((func (intern (concat mixi-object-prefix
566                               (mixi-object-name object) "-time"))))
567     (funcall func object)))
568
569 (defun mixi-object-title (object)
570   "Return the title of OBJECT."
571   (unless (mixi-object-p object)
572     (signal 'wrong-type-argument (list 'mixi-object-p object)))
573   (let ((func (intern (concat mixi-object-prefix
574                               (mixi-object-name object) "-title"))))
575     (funcall func object)))
576
577 (defun mixi-object-content (object)
578   "Return the content of OBJECT."
579   (unless (mixi-object-p object)
580     (signal 'wrong-type-argument (list 'mixi-object-p object)))
581   (let ((func (intern (concat mixi-object-prefix
582                               (mixi-object-name object) "-content"))))
583     (funcall func object)))
584
585 (defun mixi-object-set-timestamp (object timestamp)
586   "Set the timestamp of OBJECT."
587   (unless (mixi-object-p object)
588     (signal 'wrong-type-argument (list 'mixi-object-p object)))
589   (aset (cdr object) 0 timestamp))
590
591 (defmacro mixi-object-touch (object)
592   `(mixi-object-set-timestamp ,object (current-time)))
593
594 (defconst mixi-object-url-regexp
595   "/\\(show\\|view\\)_\\([a-z]+\\)\\.pl")
596
597 (defun mixi-make-object-from-url (url)
598   "Return a mixi object from URL."
599   (if (string-match mixi-object-url-regexp url)
600       (let ((name (match-string 2 url)))
601         (when (string= name "bbs")
602           (setq name "topic"))
603         (let ((func (intern (concat mixi-object-prefix "make-" name
604                                     "-from-url"))))
605           (funcall func url)))
606     (when (string-match "/home\\.pl" url)
607       (mixi-make-friend-from-url url))))
608
609 ;; Cache.
610 ;; stolen from time-date.el
611 (defun mixi-time-less-p (t1 t2)
612   "Say whether time value T1 is less than time value T2."
613   (unless (numberp (cdr t1))
614     (setq t1 (cons (car t1) (car (cdr t1)))))
615   (unless (numberp (cdr t2))
616     (setq t2 (cons (car t2) (car (cdr t2)))))
617   (or (< (car t1) (car t2))
618       (and (= (car t1) (car t2))
619            (< (cdr t1) (cdr t2)))))
620
621 (defun mixi-time-add (t1 t2)
622   "Add two time values.  One should represent a time difference."
623   (unless (numberp (cdr t1))
624     (setq t1 (cons (car t1) (car (cdr t1)))))
625   (unless (numberp (cdr t2))
626     (setq t2 (cons (car t2) (car (cdr t2)))))
627   (let ((low (+ (cdr t1) (cdr t2))))
628     (cons (+ (car t1) (car t2) (lsh low -16)) low)))
629
630 ;; stolen from time-date.el
631 (defun mixi-seconds-to-time (seconds)
632   "Convert SECONDS (a floating point number) to a time value."
633   (cons (floor seconds 65536)
634         (floor (mod seconds 65536))))
635
636 (defun mixi-cache-expired-p (object)
637   "Whether a cache of OBJECT is expired."
638   (let ((timestamp (mixi-object-timestamp object)))
639     (unless (or (null mixi-cache-expires)
640                  (null timestamp))
641       (if (numberp mixi-cache-expires)
642           (mixi-time-less-p
643            (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
644            (current-time))
645         t))))
646
647 (defun mixi-make-cache (key value table)
648   "Make a cache object and return it."
649   (let ((cache (gethash key table)))
650     (if (and cache (not (mixi-cache-expired-p cache)))
651         cache
652       (puthash key value table))))
653
654 (defconst mixi-cache-file-regexp "[a-z]+-cache$")
655 (defconst mixi-cache-regexp (concat mixi-object-prefix
656                                     mixi-cache-file-regexp))
657
658 (defun mixi-save-cache ()
659   (let ((cache-directory (expand-file-name "cache" mixi-directory)))
660     (unless (file-directory-p cache-directory)
661       (make-directory cache-directory t))
662     (let ((caches (apropos-internal mixi-cache-regexp 'boundp)))
663       (mapc (lambda (symbol)
664               (with-temp-file (expand-file-name
665                                (substring (symbol-name symbol)
666                                           (length mixi-object-prefix))
667                                cache-directory)
668                 (let ((coding-system-for-write mixi-coding-system)
669                       (cache (symbol-value symbol)))
670                   (insert "#s(hash-table size "
671                           (number-to-string (hash-table-count cache))
672                           " test equal data (\n")
673                   (maphash
674                    (lambda (key value)
675                      (let (print-level print-length)
676                        (insert (prin1-to-string key) " "
677                                (prin1-to-string value) "\n")))
678                    cache))
679                 (insert "))")))
680             caches))))
681
682 ;; stolen (and modified) from lsdb.el
683 (defun mixi-read-cache (&optional marker)
684   "Read one Lisp expression as text from MARKER, return as Lisp object."
685   (save-excursion
686     (goto-char marker)
687     (if (looking-at "^#s(")
688         (let ((end-marker
689                (progn
690                  (forward-char 2);skip "#s"
691                  (forward-sexp);move to the left paren
692                  (point-marker))))
693           (with-temp-buffer
694             (buffer-disable-undo)
695             (insert-buffer-substring (marker-buffer marker)
696                                      marker end-marker)
697             (goto-char (point-min))
698             (delete-char 2)
699             (let ((object (read (current-buffer)))
700                   data)
701               (if (eq 'hash-table (car object))
702                   (progn
703                     (setq data (plist-get (cdr object) 'data))
704                     (while data
705                       (pop data);throw it away
706                       (mixi-read-object (pop data))))
707                 object))))
708       (read marker))))
709
710 (defun mixi-load-cache ()
711   (let ((cache-directory (expand-file-name "cache" mixi-directory)))
712     (when (file-directory-p cache-directory)
713       ;; FIXME: Load friend and community first.
714       (let ((files (directory-files cache-directory t
715                                     mixi-cache-file-regexp)))
716         (mapc (lambda (file)
717                 (let ((buffer (find-file-noselect file)))
718                   (unwind-protect
719                       (save-excursion
720                         (set-buffer buffer)
721                         (goto-char (point-min))
722                         (re-search-forward "^#s(")
723                         (goto-char (match-beginning 0))
724                         (mixi-read-cache (point-marker)))
725                     (kill-buffer buffer))))
726               files)))))
727
728 ;; Friend object.
729 (defvar mixi-friend-cache (make-hash-table :test 'equal))
730 (defun mixi-make-friend (id &optional nick name sex address age birthday
731                             blood-type birthplace hobby job organization
732                             profile)
733   "Return a friend object."
734   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick name sex address
735                                                  age birthday blood-type
736                                                  birthplace hobby job
737                                                  organization profile))
738                    mixi-friend-cache))
739
740 (defun mixi-make-me ()
741   "Return a my object."
742   (unless mixi-me
743     (with-mixi-retrieve "/home.pl"
744       (if (re-search-forward mixi-my-id-regexp)
745           (setq mixi-me (mixi-make-friend (match-string 1)))
746         (signal 'error (list 'who-am-i)))))
747   mixi-me)
748
749 (defconst mixi-friend-url-regexp
750   "/show_friend\\.pl\\?id=\\([0-9]+\\)")
751
752 (defun mixi-make-friend-from-url (url)
753   "Return a friend object from URL."
754   (if (string-match mixi-friend-url-regexp url)
755       (let ((id (match-string 1 url)))
756         (mixi-make-friend id))
757     (when (string-match "/home\\.pl" url)
758       (mixi-make-me))))
759
760 (defmacro mixi-friend-p (friend)
761   `(eq (mixi-object-class ,friend) 'mixi-friend))
762
763 (defmacro mixi-friend-page (friend)
764   `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
765
766 (defconst mixi-friend-nick-regexp
767   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\r?
768 \\(.*\\)¤µ¤ó([0-9]+)")
769 (defconst mixi-friend-name-regexp
770   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>
771
772 ?<td WIDTH=345>\\(.+?\\)\\(</td>\\|<img\\)")
773 (defconst mixi-friend-sex-regexp
774   "<td BGCOLOR=#F2DDB7\\( WIDTH=80 NOWRAP\\)?><font COLOR=#996600>À­\\(&nbsp;\\| \\)ÊÌ</font></td>
775
776 ?<td WIDTH=345>\\([Ã˽÷]\\)À­\\(</td>\\|<img\\)")
777 (defconst mixi-friend-address-regexp
778   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
779 (defconst mixi-friend-age-regexp
780   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
781 (defconst mixi-friend-birthday-regexp
782   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
783 (defconst mixi-friend-blood-type-regexp
784   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
785 (defconst mixi-friend-birthplace-regexp
786   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
787 (defconst mixi-friend-hobby-regexp
788   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+?\\)\\(</td>\\|<img\\)")
789 (defconst mixi-friend-job-regexp
790   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
791 (defconst mixi-friend-organization-regexp
792   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
793 (defconst mixi-friend-profile-regexp
794   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
795
796 (defun mixi-realize-friend (friend)
797   "Realize a FRIEND."
798   ;; FIXME: Check a expiration of cache?
799   (unless (mixi-object-realized-p friend)
800     (let (buf)
801       (with-mixi-retrieve (mixi-friend-page friend)
802         (setq buf (buffer-string)))
803       (if (string-match mixi-friend-nick-regexp buf)
804           (mixi-friend-set-nick friend (match-string 1 buf))
805         (mixi-realization-error 'cannot-find-nick friend))
806       ;; For getting my profile.
807       (unless (string-match mixi-friend-name-regexp buf)
808         (with-mixi-retrieve (concat "/show_profile.pl?id="
809                                     (mixi-friend-id friend))
810           (setq buf (buffer-string))))
811       (if (string-match mixi-friend-name-regexp buf)
812             (mixi-friend-set-name friend (match-string 2 buf))
813         (mixi-realization-error 'cannot-find-name friend))
814       (if (string-match mixi-friend-sex-regexp buf)
815           (mixi-friend-set-sex friend
816                                (if (string= (match-string 3 buf) "ÃË")
817                                    'male 'female))
818         (mixi-realization-error 'cannot-find-sex friend))
819       (when (string-match mixi-friend-address-regexp buf)
820         (mixi-friend-set-address friend (match-string 1 buf)))
821       (when (string-match mixi-friend-age-regexp buf)
822         (mixi-friend-set-age
823          friend (string-to-number (match-string 2 buf))))
824       (when (string-match mixi-friend-birthday-regexp buf)
825         (mixi-friend-set-birthday
826          friend (list (string-to-number (match-string 1 buf))
827                       (string-to-number (match-string 2 buf)))))
828       (when (string-match mixi-friend-blood-type-regexp buf)
829         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
830       (when (string-match mixi-friend-birthplace-regexp buf)
831         (mixi-friend-set-birthplace friend (match-string 1 buf)))
832       (when (string-match mixi-friend-hobby-regexp buf)
833         (mixi-friend-set-hobby
834          friend (split-string (match-string 2 buf) ", ")))
835       (when (string-match mixi-friend-job-regexp buf)
836         (mixi-friend-set-job friend (match-string 2 buf)))
837       (when (string-match mixi-friend-organization-regexp buf)
838         (mixi-friend-set-organization friend (match-string 2 buf)))
839       (when (string-match mixi-friend-profile-regexp buf)
840         (mixi-friend-set-profile friend (match-string 1 buf))))
841     (mixi-object-touch friend)))
842
843 (defun mixi-friend-id (friend)
844   "Return the id of FRIEND."
845   (unless (mixi-friend-p friend)
846     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
847   (aref (cdr friend) 1))
848
849 (defun mixi-friend-nick (friend)
850   "Return the nick of FRIEND."
851   (unless (mixi-friend-p friend)
852     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
853   (unless (aref (cdr friend) 2)
854     (mixi-realize-friend friend))
855   (aref (cdr friend) 2))
856
857 (defun mixi-friend-name (friend)
858   "Return the name of FRIEND."
859   (unless (mixi-friend-p friend)
860     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
861   (mixi-realize-friend friend)
862   (aref (cdr friend) 3))
863
864 (defun mixi-friend-sex (friend)
865   "Return the sex of FRIEND."
866   (unless (mixi-friend-p friend)
867     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
868   (mixi-realize-friend friend)
869   (aref (cdr friend) 4))
870
871 (defun mixi-friend-address (friend)
872   "Return the address of FRIEND."
873   (unless (mixi-friend-p friend)
874     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
875   (mixi-realize-friend friend)
876   (aref (cdr friend) 5))
877
878 (defun mixi-friend-age (friend)
879   "Return the age of FRIEND."
880   (unless (mixi-friend-p friend)
881     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
882   (mixi-realize-friend friend)
883   (aref (cdr friend) 6))
884
885 (defun mixi-friend-birthday (friend)
886   "Return the birthday of FRIEND."
887   (unless (mixi-friend-p friend)
888     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
889   (mixi-realize-friend friend)
890   (aref (cdr friend) 7))
891
892 (defun mixi-friend-blood-type (friend)
893   "Return the blood type of FRIEND."
894   (unless (mixi-friend-p friend)
895     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
896   (mixi-realize-friend friend)
897   (aref (cdr friend) 8))
898
899 (defun mixi-friend-birthplace (friend)
900   "Return the birthplace of FRIEND."
901   (unless (mixi-friend-p friend)
902     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
903   (mixi-realize-friend friend)
904   (aref (cdr friend) 9))
905
906 (defun mixi-friend-hobby (friend)
907   "Return the hobby of FRIEND."
908   (unless (mixi-friend-p friend)
909     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
910   (mixi-realize-friend friend)
911   (aref (cdr friend) 10))
912
913 (defun mixi-friend-job (friend)
914   "Return the job of FRIEND."
915   (unless (mixi-friend-p friend)
916     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
917   (mixi-realize-friend friend)
918   (aref (cdr friend) 11))
919
920 (defun mixi-friend-organization (friend)
921   "Return the organization of FRIEND."
922   (unless (mixi-friend-p friend)
923     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
924   (mixi-realize-friend friend)
925   (aref (cdr friend) 12))
926
927 (defun mixi-friend-profile (friend)
928   "Return the pforile of FRIEND."
929   (unless (mixi-friend-p friend)
930     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
931   (mixi-realize-friend friend)
932   (aref (cdr friend) 13))
933
934 (defun mixi-friend-set-nick (friend nick)
935   "Set the nick of FRIEND."
936   (unless (mixi-friend-p friend)
937     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
938   (aset (cdr friend) 2 nick))
939
940 (defun mixi-friend-set-name (friend name)
941   "Set the name of FRIEND."
942   (unless (mixi-friend-p friend)
943     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
944   (aset (cdr friend) 3 name))
945
946 (defun mixi-friend-set-sex (friend sex)
947   "Set the sex of FRIEND."
948   (unless (mixi-friend-p friend)
949     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
950   (aset (cdr friend) 4 sex))
951
952 (defun mixi-friend-set-address (friend address)
953   "Set the address of FRIEND."
954   (unless (mixi-friend-p friend)
955     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
956   (aset (cdr friend) 5 address))
957
958 (defun mixi-friend-set-age (friend age)
959   "Set the age of FRIEND."
960   (unless (mixi-friend-p friend)
961     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
962   (aset (cdr friend) 6 age))
963
964 (defun mixi-friend-set-birthday (friend birthday)
965   "Set the birthday of FRIEND."
966   (unless (mixi-friend-p friend)
967     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
968   (aset (cdr friend) 7 birthday))
969
970 (defun mixi-friend-set-blood-type (friend blood-type)
971   "Set the blood type of FRIEND."
972   (unless (mixi-friend-p friend)
973     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
974   (aset (cdr friend) 8 blood-type))
975
976 (defun mixi-friend-set-birthplace (friend birthplace)
977   "Set the birthplace of FRIEND."
978   (unless (mixi-friend-p friend)
979     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
980   (aset (cdr friend) 9 birthplace))
981
982 (defun mixi-friend-set-hobby (friend hobby)
983   "Set the hobby of FRIEND."
984   (unless (mixi-friend-p friend)
985     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
986   (aset (cdr friend) 10 hobby))
987
988 (defun mixi-friend-set-job (friend job)
989   "Set the job of FRIEND."
990   (unless (mixi-friend-p friend)
991     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
992   (aset (cdr friend) 11 job))
993
994 (defun mixi-friend-set-organization (friend organization)
995   "Set the organization of FRIEND."
996   (unless (mixi-friend-p friend)
997     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
998   (aset (cdr friend) 12 organization))
999
1000 (defun mixi-friend-set-profile (friend profile)
1001   "Set the profile of FRIEND."
1002   (unless (mixi-friend-p friend)
1003     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1004   (aset (cdr friend) 13 profile))
1005
1006 (defmacro mixi-friend-list-page (&optional friend)
1007   `(concat "/list_friend.pl?page=%d"
1008            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1009
1010 (defconst mixi-friend-list-id-regexp
1011   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
1012 (defconst mixi-friend-list-nick-regexp
1013   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
1014
1015 (defun mixi-get-friends (&rest friend-or-range)
1016   "Get friends of FRIEND."
1017   (when (> (length friend-or-range) 2)
1018     (signal 'wrong-number-of-arguments (list 'mixi-get-friends
1019                                              (length friend-or-range))))
1020   (let ((friend (nth 0 friend-or-range))
1021         (range (nth 1 friend-or-range)))
1022     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1023       (setq friend (nth 1 friend-or-range))
1024       (setq range (nth 0 friend-or-range)))
1025     (unless (or (null friend) (mixi-friend-p friend))
1026       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1027     (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
1028                                        mixi-friend-list-id-regexp
1029                                        range))
1030           (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
1031                                          mixi-friend-list-nick-regexp
1032                                          range)))
1033       (let ((index 0)
1034             ret)
1035         (while (< index (length ids))
1036           (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
1037                                             (nth 0 (nth index nicks))) ret))
1038           (incf index))
1039         (reverse ret)))))
1040
1041 ;; Favorite.
1042 (defmacro mixi-favorite-list-page ()
1043   `(concat "/list_bookmark.pl?page=%d"))
1044
1045 (defconst mixi-favorite-list-id-regexp
1046   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
1047 (defconst mixi-favorite-list-nick-regexp
1048   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
1049 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
1050
1051 (defun mixi-get-favorites (&optional range)
1052   "Get favorites."
1053   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
1054                                      mixi-favorite-list-id-regexp
1055                                      range))
1056         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
1057                                        mixi-favorite-list-nick-regexp
1058                                        range)))
1059     (let ((index 0)
1060           ret)
1061       (while (< index (length ids))
1062         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
1063                                           (nth 0 (nth index nicks))) ret))
1064         (incf index))
1065       (reverse ret))))
1066
1067 ;; Log object.
1068 (defun mixi-make-log (friend time)
1069   "Return a log object."
1070   (cons 'mixi-log (vector friend time)))
1071
1072 (defmacro mixi-log-p (log)
1073   `(eq (mixi-object-class ,log) 'mixi-log))
1074
1075 (defun mixi-log-friend (log)
1076   "Return the friend of LOG."
1077   (unless (mixi-log-p log)
1078     (signal 'wrong-type-argument (list 'mixi-log-p log)))
1079   (aref (cdr log) 0))
1080
1081 (defun mixi-log-time (log)
1082   "Return the time of LOG."
1083   (unless (mixi-log-p log)
1084     (signal 'wrong-type-argument (list 'mixi-log-p log)))
1085   (aref (cdr log) 1))
1086
1087 (defmacro mixi-log-list-page ()
1088   `(concat "/show_log.pl"))
1089
1090 (defconst mixi-log-list-regexp
1091   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a></li>")
1092
1093 (defun mixi-get-logs (&optional range)
1094   "Get logs."
1095   (let ((items (mixi-get-matched-items (mixi-log-list-page)
1096                                        mixi-log-list-regexp
1097                                        range)))
1098     (mapcar (lambda (item)
1099               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
1100                              (encode-time 0
1101                                           (string-to-number (nth 4 item))
1102                                           (string-to-number (nth 3 item))
1103                                           (string-to-number (nth 2 item))
1104                                           (string-to-number (nth 1 item))
1105                                           (string-to-number (nth 0 item)))))
1106             items)))
1107
1108 ;; Diary object.
1109 (defvar mixi-diary-cache (make-hash-table :test 'equal))
1110 (defun mixi-make-diary (owner id &optional time title content)
1111   "Return a diary object."
1112   (let ((owner (or owner (mixi-make-me))))
1113     (mixi-make-cache (list (mixi-friend-id owner) id)
1114                      (cons 'mixi-diary (vector nil owner id time title
1115                                                content))
1116                      mixi-diary-cache)))
1117
1118 (defconst mixi-diary-url-regexp
1119   "/view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)")
1120
1121 (defun mixi-make-diary-from-url (url)
1122   "Return a diary object from URL."
1123   (when (string-match mixi-diary-url-regexp url)
1124     (let ((id (match-string 1 url))
1125           (owner-id (match-string 2 url)))
1126       (mixi-make-diary (mixi-make-friend owner-id) id))))
1127
1128 (defmacro mixi-diary-p (diary)
1129   `(eq (mixi-object-class ,diary) 'mixi-diary))
1130
1131 (defmacro mixi-diary-page (diary)
1132   `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
1133            "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
1134
1135 (defconst mixi-diary-closed-regexp
1136   "<td>ͧ¿Í\\(¤Îͧ¿Í\\)?¤Þ¤Ç¸ø³«¤Î¤¿¤áÆɤळ¤È¤¬½ÐÍè¤Þ¤»¤ó¡£</td></tr>")
1137 (defconst mixi-diary-owner-nick-regexp
1138   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+?\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
1139 (defconst mixi-diary-time-regexp
1140   "<td \\(align\\|ALIGN\\)=\"?center\"? \\(rowspan\\|ROWSPAN\\)=\"?[23]\"? \\(nowrap=\"nowrap\"\\|NOWRAP\\) \\(width\\|WIDTH\\)=\"?95\"? bgcolor=\"?#FFD8B0\"?>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(<br />\\|<br>\\)\\([0-9]+\\):\\([0-9]+\\)</td>")
1141 (defconst mixi-diary-title-regexp
1142   "<td \\(bgcolor\\|BGCOLOR\\)=\"?#FFF4E0\"? width=\"?430\"?>&nbsp;\\([^<]+\\)</td>")
1143 (defconst mixi-diary-content-regexp
1144   "<td \\(class\\|CLASS\\)=\"?h12\"?>\\(.*\\)</td>")
1145
1146 (defun mixi-realize-diary (diary)
1147   "Realize a DIARY."
1148   ;; FIXME: Check a expiration of cache?
1149   (unless (mixi-object-realized-p diary)
1150     (with-mixi-retrieve (mixi-diary-page diary)
1151       (unless (re-search-forward mixi-diary-closed-regexp nil t)
1152         (if (re-search-forward mixi-diary-owner-nick-regexp nil t)
1153             (mixi-friend-set-nick (mixi-diary-owner diary)
1154                                   (match-string 1))
1155           (mixi-realization-error 'cannot-find-owner-nick diary))
1156         (if (re-search-forward mixi-diary-time-regexp nil t)
1157             (mixi-diary-set-time
1158              diary (encode-time 0 (string-to-number (match-string 10))
1159                                 (string-to-number (match-string 9))
1160                                 (string-to-number (match-string 7))
1161                                 (string-to-number (match-string 6))
1162                                 (string-to-number (match-string 5))))
1163           (mixi-realization-error 'cannot-find-time diary))
1164         (if (re-search-forward mixi-diary-title-regexp nil t)
1165             (mixi-diary-set-title diary (match-string 2))
1166           (mixi-realization-error 'cannot-find-title diary))
1167         (if (re-search-forward mixi-diary-content-regexp nil t)
1168             (mixi-diary-set-content diary (match-string 2))
1169           (mixi-realization-error 'cannot-find-content diary))))
1170     (mixi-object-touch diary)))
1171
1172 (defun mixi-diary-owner (diary)
1173   "Return the owner of DIARY."
1174   (unless (mixi-diary-p diary)
1175     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1176   (aref (cdr diary) 1))
1177
1178 (defun mixi-diary-id (diary)
1179   "Return the id of DIARY."
1180   (unless (mixi-diary-p diary)
1181     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1182   (aref (cdr diary) 2))
1183
1184 (defun mixi-diary-time (diary)
1185   "Return the time of DIARY."
1186   (unless (mixi-diary-p diary)
1187     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1188   (unless (aref (cdr diary) 3)
1189     (mixi-realize-diary diary))
1190   (aref (cdr diary) 3))
1191
1192 (defun mixi-diary-title (diary)
1193   "Return the title of DIARY."
1194   (unless (mixi-diary-p diary)
1195     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1196   (unless (aref (cdr diary) 4)
1197     (mixi-realize-diary diary))
1198   (aref (cdr diary) 4))
1199
1200 (defun mixi-diary-content (diary)
1201   "Return the content of DIARY."
1202   (unless (mixi-diary-p diary)
1203     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1204   (mixi-realize-diary diary)
1205   (aref (cdr diary) 5))
1206
1207 (defun mixi-diary-set-time (diary time)
1208   "Set the time of DIARY."
1209   (unless (mixi-diary-p diary)
1210     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1211   (aset (cdr diary) 3 time))
1212
1213 (defun mixi-diary-set-title (diary title)
1214   "Set the title of DIARY."
1215   (unless (mixi-diary-p diary)
1216     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1217   (aset (cdr diary) 4 title))
1218
1219 (defun mixi-diary-set-content (diary content)
1220   "Set the content of DIARY."
1221   (unless (mixi-diary-p diary)
1222     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1223   (aset (cdr diary) 5 content))
1224
1225 (defmacro mixi-diary-list-page (&optional friend)
1226   `(concat "/list_diary.pl?page=%d"
1227            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1228
1229 (defconst mixi-diary-list-regexp
1230   "<tr VALIGN=top>
1231 <td ALIGN=center ROWSPAN=3 NOWRAP bgcolor=#F2DDB7><font COLOR=#996600>\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</font>\\(<br><input type=\"checkbox\" name=\"diary_id\" value=\"[0-9]+\">\\|\\)</td>
1232 <td bgcolor=\"#FFF4E0\">&nbsp;<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">\\(.*\\)</a></td>")
1233
1234 (defun mixi-get-diaries (&rest friend-or-range)
1235   "Get diaries of FRIEND."
1236   (when (> (length friend-or-range) 2)
1237     (signal 'wrong-number-of-arguments
1238             (list 'mixi-get-diaries (length friend-or-range))))
1239   (let ((friend (nth 0 friend-or-range))
1240         (range (nth 1 friend-or-range)))
1241     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1242       (setq friend (nth 1 friend-or-range))
1243       (setq range (nth 0 friend-or-range)))
1244     (unless (or (null friend) (mixi-friend-p friend))
1245       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1246     (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
1247                                          mixi-diary-list-regexp
1248                                          range))
1249           (year (nth 5 (decode-time (current-time))))
1250           (month (nth 4 (decode-time (current-time)))))
1251       (mapcar (lambda (item)
1252                 (let ((month-of-item (string-to-number (nth 0 item))))
1253                   (when (> month-of-item month)
1254                     (decf year))
1255                   (setq month month-of-item)
1256                   (mixi-make-diary friend (nth 5 item)
1257                                    (encode-time
1258                                     0 (string-to-number (nth 3 item))
1259                                     (string-to-number (nth 2 item))
1260                                     (string-to-number (nth 1 item))
1261                                     month year)
1262                                    (nth 6 item))))
1263               items))))
1264
1265 (defmacro mixi-new-diary-list-page ()
1266   `(concat "/new_friend_diary.pl?page=%d"))
1267
1268 (defconst mixi-new-diary-list-regexp
1269   "<td WIDTH=180><img src=http://img\\.mixi\\.jp/img/pen\\.gif ALIGN=left WIDTH=14 HEIGHT=16>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td>
1270 <td WIDTH=450><a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>\\(.+\\)</a> (\\(.*\\)) ")
1271
1272 (defun mixi-get-new-diaries (&optional range)
1273   "Get new diaries."
1274   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
1275                                        mixi-new-diary-list-regexp
1276                                        range)))
1277     (mapcar (lambda (item)
1278               (mixi-make-diary (mixi-make-friend (nth 6 item) (nth 8 item))
1279                                (nth 5 item)
1280                                (encode-time
1281                                 0 (string-to-number (nth 4 item))
1282                                 (string-to-number (nth 3 item))
1283                                 (string-to-number (nth 2 item))
1284                                 (string-to-number (nth 1 item))
1285                                 (string-to-number (nth 0 item)))
1286                                (nth 7 item)))
1287             items)))
1288
1289 (defmacro mixi-search-diary-list-page (keyword)
1290   `(concat "/search_diary.pl?page=%d&submit=search&keyword="
1291              (mixi-url-encode-and-quote-percent-string ,keyword)))
1292
1293 (defconst mixi-search-diary-list-regexp
1294   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
1295 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.*\\)
1296
1297 </td></tr>
1298
1299 <tr>
1300 <td BGCOLOR=#FDF9F2><font COLOR=#996600>¥¿¥¤¥È¥ë</font></td>
1301 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>
1302
1303 <tr>
1304 <td BGCOLOR=#FDF9F2><font COLOR=#996600>ËÜ&nbsp;&nbsp;ʸ</font></td>
1305 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.*\\)</td></tr>
1306
1307
1308 <tr>
1309 <td NOWRAP BGCOLOR=#FDF9F2 WIDTH=80><font COLOR=#996600>ºîÀ®Æü»þ</font></td>
1310 <td BGCOLOR=#FFFFFF WIDTH=220>\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td>
1311 <td ALIGN=center BGCOLOR=#FDF9F2 width=250><a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)\"><img src=http://img\\.mixi\\.jp/img/shbtn\\.gif ALT=¾ÜºÙ¤ò¸«¤ë BORDER=0 WIDTH=104 HEIGHT=19></a></td></tr>
1312 </table>
1313 </td></tr></table>")
1314
1315 (defun mixi-search-diaries (keyword &optional range)
1316   (let ((items (mixi-get-matched-items (mixi-search-diary-list-page keyword)
1317                                        mixi-search-diary-list-regexp
1318                                        range))
1319         (year (nth 5 (decode-time (current-time))))
1320         (month (nth 4 (decode-time (current-time)))))
1321     (mapcar (lambda (item)
1322                 (let ((month-of-item (string-to-number (nth 3 item))))
1323                   (when (> month-of-item month)
1324                     (decf year))
1325                   (setq month month-of-item)
1326                   (mixi-make-diary (mixi-make-friend (nth 8 item) (nth 0 item))
1327                                    (nth 7 item)
1328                                    (encode-time
1329                                     0 (string-to-number (nth 6 item))
1330                                     (string-to-number (nth 5 item))
1331                                     (string-to-number (nth 4 item))
1332                                     month year)
1333                                    (nth 1 item)
1334                                    (nth 2 item))))
1335             items)))
1336
1337 (defmacro mixi-post-diary-page ()
1338   `(concat "/add_diary.pl"))
1339
1340 (defconst mixi-post-key-regexp
1341   "<input type=\"?hidden\"? name=\"?post_key\"? value=\"\\([a-z0-9]+\\)\">")
1342 (defconst mixi-post-succeed-regexp
1343   "<b>\\(ºîÀ®\\|½ñ¤­¹þ¤ß\\)¤¬´°Î»¤·¤Þ¤·¤¿¡£È¿±Ç¤Ë»þ´Ö¤¬¤«¤«¤ë¤³¤È¤¬¤¢¤ê¤Þ¤¹¤Î¤Ç¡¢É½¼¨¤µ¤ì¤Æ¤¤¤Ê¤¤¾ì¹ç¤Ï¾¯¡¹¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£</b>")
1344
1345 ;; FIXME: Support photos.
1346 (defun mixi-post-diary (title content)
1347   "Post a diary."
1348   (unless (stringp title)
1349     (signal 'wrong-type-argument (list 'stringp title)))
1350   (unless (stringp content)
1351     (signal 'wrong-type-argument (list 'stringp content)))
1352   (let ((fields `(("id" . ,(mixi-friend-id (mixi-make-me)))
1353                   ("diary_title" . ,title)
1354                   ("diary_body" . ,content)
1355                   ("submit" . "main")))
1356         post-key)
1357     (with-mixi-post-form (mixi-post-diary-page) fields
1358       (if (re-search-forward mixi-post-key-regexp nil t)
1359           (setq post-key (match-string 1))
1360         (mixi-post-error 'cannot-find-key)))
1361     (setq fields `(("post_key" . ,post-key)
1362                    ("id" . ,(mixi-friend-id (mixi-make-me)))
1363                    ("diary_title" . ,title)
1364                    ("diary_body" . ,content)
1365                    ("submit" . "confirm")))
1366     (with-mixi-post-form (mixi-post-diary-page) fields
1367       (unless (re-search-forward mixi-post-succeed-regexp nil t)
1368         (mixi-post-error 'cannot-find-succeed)))))
1369
1370 ;; Community object.
1371 (defvar mixi-community-cache (make-hash-table :test 'equal))
1372 (defun mixi-make-community (id &optional name birthday owner category members
1373                                open-level authority description)
1374   "Return a community object."
1375   (mixi-make-cache id (cons 'mixi-community (vector nil id name birthday owner
1376                                                     category members
1377                                                     open-level authority
1378                                                     description))
1379                    mixi-community-cache))
1380
1381 (defconst mixi-community-url-regexp
1382   "/view_community\\.pl\\?id=\\([0-9]+\\)")
1383
1384 (defun mixi-make-community-from-url (url)
1385   "Return a community object from URL."
1386   (when (string-match mixi-community-url-regexp url)
1387     (let ((id (match-string 1 url)))
1388       (mixi-make-community id))))
1389
1390 (defmacro mixi-community-p (community)
1391   `(eq (mixi-object-class ,community) 'mixi-community))
1392
1393 (defmacro mixi-community-page (community)
1394   `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
1395
1396 ;; FIXME: Not community specific.
1397 (defconst mixi-community-nodata-regexp
1398   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1399 (defconst mixi-community-name-regexp
1400   "<td WIDTH=345>\\(.*\\)</td></tr>")
1401 (defconst mixi-community-birthday-regexp
1402   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>³«ÀßÆü</font></td>\r
1403 <td WIDTH=345>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1404 ;; FIXME: Care when the owner has seceded.
1405 (defconst mixi-community-owner-regexp
1406   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>´ÉÍý¿Í</font></td>\r
1407 <td WIDTH=345>\r
1408 <table style=\"width: 100%;\"><tr><td>\r
1409 <a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\\(.*\\)</a>")
1410 (defconst mixi-community-category-regexp
1411   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\r
1412 <td WIDTH=345>\\([^<]+\\)</td>")
1413 (defconst mixi-community-members-regexp
1414   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\r
1415 <td WIDTH=345>\\([0-9]+\\)¿Í</td></tr>")
1416 (defconst mixi-community-open-level-regexp
1417   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>\r
1418 <td WIDTH=345>\\(.+\\)</td></tr>")
1419 (defconst mixi-community-authority-regexp
1420   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\r
1421 <td WIDTH=345>\\(.+\\)</td></tr>")
1422 (defconst mixi-community-description-regexp
1423   "<td CLASS=h120 WIDTH=345>\\(.+\\)</td>")
1424
1425 (defun mixi-realize-community (community)
1426   "Realize a COMMUNITY."
1427   ;; FIXME: Check a expiration of cache?
1428   (unless (mixi-object-realized-p community)
1429     (with-mixi-retrieve (mixi-community-page community)
1430       (if (re-search-forward mixi-community-nodata-regexp nil t)
1431           ;; FIXME: Set all members?
1432           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1433         (if (re-search-forward mixi-community-name-regexp nil t)
1434             (mixi-community-set-name community (match-string 1))
1435           (mixi-realization-error 'cannot-find-name community))
1436         (if (re-search-forward mixi-community-birthday-regexp nil t)
1437             (mixi-community-set-birthday
1438              community
1439              (encode-time 0 0 0 (string-to-number (match-string 3))
1440                           (string-to-number (match-string 2))
1441                           (string-to-number (match-string 1))))
1442           (mixi-realization-error 'cannot-find-birthday community))
1443         (if (re-search-forward mixi-community-owner-regexp nil t)
1444             (if (string= (match-string 1) "home.pl")
1445                 (mixi-community-set-owner community (mixi-make-me))
1446               (mixi-community-set-owner
1447                community (mixi-make-friend (match-string 2)
1448                                            (match-string 3))))
1449           (mixi-realization-error 'cannot-find-owner community))
1450         (if (re-search-forward mixi-community-category-regexp nil t)
1451             (mixi-community-set-category community (match-string 1))
1452           (mixi-realization-error 'cannot-find-category community))
1453         (if (re-search-forward mixi-community-members-regexp nil t)
1454             (mixi-community-set-members
1455              community (string-to-number (match-string 1)))
1456           (mixi-realization-error 'cannot-find-members community))
1457         (if (re-search-forward mixi-community-open-level-regexp nil t)
1458             (mixi-community-set-open-level community (match-string 1))
1459           (mixi-realization-error 'cannot-find-open-level community))
1460         (if (re-search-forward mixi-community-authority-regexp nil t)
1461             (mixi-community-set-authority community (match-string 1))
1462           (mixi-realization-error 'cannot-find-authority community))
1463         (if (re-search-forward mixi-community-description-regexp nil t)
1464             (mixi-community-set-description community (match-string 1))
1465           (mixi-realization-error 'cannot-find-description community))))
1466     (mixi-object-touch community)))
1467
1468 (defun mixi-community-id (community)
1469   "Return the id of COMMUNITY."
1470   (unless (mixi-community-p community)
1471     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1472   (aref (cdr community) 1))
1473
1474 (defun mixi-community-name (community)
1475   "Return the name of COMMUNITY."
1476   (unless (mixi-community-p community)
1477     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1478   (unless (aref (cdr community) 2)
1479     (mixi-realize-community community))
1480   (aref (cdr community) 2))
1481
1482 (defun mixi-community-birthday (community)
1483   "Return the birthday of COMMUNITY."
1484   (unless (mixi-community-p community)
1485     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1486   (mixi-realize-community community)
1487   (aref (cdr community) 3))
1488
1489 (defun mixi-community-owner (community)
1490   "Return the owner of COMMUNITY."
1491   (unless (mixi-community-p community)
1492     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1493   (mixi-realize-community community)
1494   (aref (cdr community) 4))
1495
1496 (defun mixi-community-category (community)
1497   "Return the category of COMMUNITY."
1498   (unless (mixi-community-p community)
1499     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1500   (mixi-realize-community community)
1501   (aref (cdr community) 5))
1502
1503 (defun mixi-community-members (community)
1504   "Return the members of COMMUNITY."
1505   (unless (mixi-community-p community)
1506     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1507   (mixi-realize-community community)
1508   (aref (cdr community) 6))
1509
1510 (defun mixi-community-open-level (community)
1511   "Return the open-level of COMMUNITY."
1512   (unless (mixi-community-p community)
1513     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1514   (mixi-realize-community community)
1515   (aref (cdr community) 7))
1516
1517 (defun mixi-community-authority (community)
1518   "Return the authority of COMMUNITY."
1519   (unless (mixi-community-p community)
1520     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1521   (mixi-realize-community community)
1522   (aref (cdr community) 8))
1523
1524 (defun mixi-community-description (community)
1525   "Return the description of COMMUNITY."
1526   (unless (mixi-community-p community)
1527     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1528   (mixi-realize-community community)
1529   (aref (cdr community) 9))
1530
1531 (defun mixi-community-set-name (community name)
1532   "Set the name of COMMUNITY."
1533   (unless (mixi-community-p community)
1534     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1535   (aset (cdr community) 2 name))
1536
1537 (defun mixi-community-set-birthday (community birthday)
1538   "Set the birthday of COMMUNITY."
1539   (unless (mixi-community-p community)
1540     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1541   (aset (cdr community) 3 birthday))
1542
1543 (defun mixi-community-set-owner (community owner)
1544   "Set the owner of COMMUNITY."
1545   (unless (mixi-community-p community)
1546     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1547   (unless (mixi-friend-p owner)
1548     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1549   (aset (cdr community) 4 owner))
1550
1551 (defun mixi-community-set-category (community category)
1552   "Set the category of COMMUNITY."
1553   (unless (mixi-community-p community)
1554     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1555   (aset (cdr community) 5 category))
1556
1557 (defun mixi-community-set-members (community members)
1558   "Set the name of COMMUNITY."
1559   (unless (mixi-community-p community)
1560     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1561   (aset (cdr community) 6 members))
1562
1563 (defun mixi-community-set-open-level (community open-level)
1564   "Set the name of COMMUNITY."
1565   (unless (mixi-community-p community)
1566     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1567   (aset (cdr community) 7 open-level))
1568
1569 (defun mixi-community-set-authority (community authority)
1570   "Set the name of COMMUNITY."
1571   (unless (mixi-community-p community)
1572     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1573   (aset (cdr community) 8 authority))
1574
1575 (defun mixi-community-set-description (community description)
1576   "Set the name of COMMUNITY."
1577   (unless (mixi-community-p community)
1578     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1579   (aset (cdr community) 9 description))
1580
1581 (defmacro mixi-community-list-page (&optional friend)
1582   `(concat "/list_community.pl?page=%d"
1583            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1584
1585 (defconst mixi-community-list-id-regexp
1586   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1587 (defconst mixi-community-list-name-regexp
1588   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1589
1590 (defun mixi-get-communities (&rest friend-or-range)
1591   "Get communities of FRIEND."
1592   (when (> (length friend-or-range) 2)
1593     (signal 'wrong-number-of-arguments
1594             (list 'mixi-get-communities (length friend-or-range))))
1595   (let ((friend (nth 0 friend-or-range))
1596         (range (nth 1 friend-or-range)))
1597     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1598       (setq friend (nth 1 friend-or-range))
1599       (setq range (nth 0 friend-or-range)))
1600     (unless (or (null friend) (mixi-friend-p friend))
1601       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1602     (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1603                                        mixi-community-list-id-regexp
1604                                        range))
1605           (names (mixi-get-matched-items (mixi-community-list-page friend)
1606                                          mixi-community-list-name-regexp
1607                                          range)))
1608       (let ((index 0)
1609             ret)
1610         (while (< index (length ids))
1611           (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1612                                                (nth 0 (nth index names))) ret))
1613           (incf index))
1614         (reverse ret)))))
1615
1616 (defmacro mixi-search-community-list-page (keyword)
1617   `(concat "/search_community.pl?page=%d&&sort=date&type=com&submit=main"
1618            "&keyword=" (mixi-url-encode-and-quote-percent-string ,keyword)
1619            "&category_id=0"))
1620
1621 (defconst mixi-search-community-list-regexp
1622   "<td WIDTH=90 VALIGN=top ROWSPAN=4 ALIGN=center background=http://img\\.mixi\\.jp/img/bg_line\\.gif><a href=\"view_community\\.pl\\?id=\\([0-9]+\\)\"><img SRC=\"http://img-c[0-9]+\\.mixi\\.jp/photo/comm/[^.]+\\.jpg\" VSPACE=3 border=0></a></td>
1623 <td NOWRAP WIDTH=90 BGCOLOR=#FDF9F2><font COLOR=#996600>¥³¥ß¥å¥Ë¥Æ¥£Ì¾</font></td>
1624 <td COLSPAN=2 WIDTH=370 BGCOLOR=#FFFFFF>\\([^<]+\\)</td></tr>")
1625
1626 ;; FIXME: Support category.
1627 (defun mixi-search-communities (keyword &optional range)
1628   (let ((items (mixi-get-matched-items (mixi-search-community-list-page
1629                                         keyword)
1630                                        mixi-search-community-list-regexp
1631                                        range)))
1632     (mapcar (lambda (item)
1633               (mixi-make-community (nth 0 item) (nth 1 item)))
1634             items)))
1635
1636 ;; Topic object.
1637 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1638 (defun mixi-make-topic (community id &optional time title owner content)
1639   "Return a topic object."
1640   (mixi-make-cache (list (mixi-community-id community) id)
1641                    (cons 'mixi-topic (vector nil community id time title owner
1642                                              content))
1643                    mixi-topic-cache))
1644
1645 (defconst mixi-topic-url-regexp
1646   "/view_bbs\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1647
1648 (defun mixi-make-topic-from-url (url)
1649   "Return a topic object from URL."
1650   (when (string-match mixi-topic-url-regexp url)
1651     (let ((id (match-string 1 url))
1652           (community-id (match-string 3 url)))
1653       (mixi-make-topic (mixi-make-community community-id) id))))
1654
1655 (defmacro mixi-topic-p (topic)
1656   `(eq (mixi-object-class ,topic) 'mixi-topic))
1657
1658 (defmacro mixi-topic-page (topic)
1659   `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
1660            "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
1661
1662 (defconst mixi-topic-community-regexp
1663   "<td width=\"595\" background=\"http://img\\.mixi\\.jp/img/bg_w\\.gif\"><b>\\[\\(.+\\)\\] ¥È¥Ô¥Ã¥¯</b></td>")
1664 (defconst mixi-topic-time-regexp
1665   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1666 (defconst mixi-topic-title-regexp
1667   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1668 (defconst mixi-topic-owner-regexp
1669   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*?\\)\\(¤µ¤ó\\)?</a>")
1670 (defconst mixi-topic-content-regexp
1671   "<table width=\"500\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\"><tr><td class=\"h120\"><table><tr>\\(<td width=\"130\" height=\"140\" align=\"center\" valign=\"middle\"><a href=\"javascript:void(0)\" onClick=\"MM_openBrWindow('show_bbs_picture\\.pl\\?id=[0-9]+&comm_id=[0-9]+&number=[0-9]+','pict','width=680,height=660,toolbar=no,scrollbars=yes,left=5,top=5')\"><img src=\"http://ic[0-9]+\\.mixi\\.jp/[^.]+\\.jpg\" border=\"0\"></a></td>\n\\)*</tr></table>\\(.+\\)</td></tr></table>")
1672
1673 (defun mixi-realize-topic (topic)
1674   "Realize a TOPIC."
1675   ;; FIXME: Check a expiration of cache?
1676   (unless (mixi-object-realized-p topic)
1677     (with-mixi-retrieve (mixi-topic-page topic)
1678       (if (re-search-forward mixi-topic-community-regexp nil t)
1679           (mixi-community-set-name (mixi-topic-community topic)
1680                                    (match-string 1))
1681         (mixi-realization-error 'cannot-find-community topic))
1682       (if (re-search-forward mixi-topic-time-regexp nil t)
1683           (mixi-topic-set-time
1684            topic (encode-time 0 (string-to-number (match-string 5))
1685                               (string-to-number (match-string 4))
1686                               (string-to-number (match-string 3))
1687                               (string-to-number (match-string 2))
1688                               (string-to-number (match-string 1))))
1689         (mixi-realization-error 'cannot-find-time topic))
1690       (if (re-search-forward mixi-topic-title-regexp nil t)
1691           (mixi-topic-set-title topic (match-string 1))
1692         (mixi-realization-error 'cannot-find-title topic))
1693       (if (re-search-forward mixi-topic-owner-regexp nil t)
1694           (mixi-topic-set-owner topic
1695                                 (mixi-make-friend (match-string 1)
1696                                                   (match-string 2)))
1697         (mixi-realization-error 'cannot-find-owner topic))
1698       (if (re-search-forward mixi-topic-content-regexp nil t)
1699           (mixi-topic-set-content topic (match-string 2))
1700         (mixi-realization-error 'cannot-find-content topic)))
1701     (mixi-object-touch topic)))
1702
1703 (defun mixi-topic-community (topic)
1704   "Return the community of TOPIC."
1705   (unless (mixi-topic-p topic)
1706     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1707   (aref (cdr topic) 1))
1708
1709 (defun mixi-topic-id (topic)
1710   "Return the id of TOPIC."
1711   (unless (mixi-topic-p topic)
1712     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1713   (aref (cdr topic) 2))
1714
1715 (defun mixi-topic-time (topic)
1716   "Return the time of TOPIC."
1717   (unless (mixi-topic-p topic)
1718     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1719   (mixi-realize-topic topic)
1720   (aref (cdr topic) 3))
1721
1722 (defun mixi-topic-title (topic)
1723   "Return the title of TOPIC."
1724   (unless (mixi-topic-p topic)
1725     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1726   (mixi-realize-topic topic)
1727   (aref (cdr topic) 4))
1728
1729 (defun mixi-topic-owner (topic)
1730   "Return the owner of TOPIC."
1731   (unless (mixi-topic-p topic)
1732     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1733   (mixi-realize-topic topic)
1734   (aref (cdr topic) 5))
1735
1736 (defun mixi-topic-content (topic)
1737   "Return the content of TOPIC."
1738   (unless (mixi-topic-p topic)
1739     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1740   (mixi-realize-topic topic)
1741   (aref (cdr topic) 6))
1742
1743 (defun mixi-topic-set-time (topic time)
1744   "Set the time of TOPIC."
1745   (unless (mixi-topic-p topic)
1746     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1747   (aset (cdr topic) 3 time))
1748
1749 (defun mixi-topic-set-title (topic title)
1750   "Set the title of TOPIC."
1751   (unless (mixi-topic-p topic)
1752     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1753   (aset (cdr topic) 4 title))
1754
1755 (defun mixi-topic-set-owner (topic owner)
1756   "Set the owner of TOPIC."
1757   (unless (mixi-topic-p topic)
1758     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1759   (unless (mixi-friend-p owner)
1760     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1761   (aset (cdr topic) 5 owner))
1762
1763 (defun mixi-topic-set-content (topic content)
1764   "Set the content of TOPIC."
1765   (unless (mixi-topic-p topic)
1766     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1767   (aset (cdr topic) 6 content))
1768
1769 (defmacro mixi-post-topic-page (community)
1770   `(concat "/add_bbs.pl?id=" (mixi-community-id community)))
1771
1772 ;; FIXME: Support photos.
1773 (defun mixi-post-topic (community title content)
1774   "Post a topic to COMMUNITY."
1775   (unless (mixi-community-p community)
1776     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1777   (unless (stringp title)
1778     (signal 'wrong-type-argument (list 'stringp title)))
1779   (unless (stringp content)
1780     (signal 'wrong-type-argument (list 'stringp content)))
1781   (let ((fields `(("bbs_title" . ,title)
1782                   ("bbs_body" . ,content)
1783                   ("submit" . "main")))
1784         post-key)
1785     (with-mixi-post-form (mixi-post-topic-page community) fields
1786       (if (re-search-forward mixi-post-key-regexp nil t)
1787           (setq post-key (match-string 1))
1788         (mixi-post-error 'cannot-find-key community)))
1789     (setq fields `(("post_key" . ,post-key)
1790                    ("bbs_title" . ,title)
1791                    ("bbs_body" . ,content)
1792                    ("submit" . "confirm")))
1793     (with-mixi-post-form (mixi-post-topic-page community) fields
1794       (unless (re-search-forward mixi-post-succeed-regexp nil t)
1795         (mixi-post-error 'cannot-find-succeed community)))))
1796
1797 ;; Event object.
1798 (defvar mixi-event-cache (make-hash-table :test 'equal))
1799 (defun mixi-make-event (community id &optional time title owner date place
1800                                   detail limit members)
1801   "Return a event object."
1802   (mixi-make-cache (list (mixi-community-id community) id)
1803                    (cons 'mixi-event (vector nil community id time title owner
1804                                              date place detail limit members))
1805                    mixi-event-cache))
1806
1807 (defconst mixi-event-url-regexp
1808   "/view_event\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1809
1810 (defun mixi-make-event-from-url (url)
1811   "Return a event object from URL."
1812   (when (string-match mixi-event-url-regexp url)
1813     (let ((id (match-string 1 url))
1814           (community-id (match-string 3 url)))
1815       (mixi-make-event (mixi-make-community community-id) id))))
1816
1817 (defmacro mixi-event-p (event)
1818   `(eq (mixi-object-class ,event) 'mixi-event))
1819
1820 (defmacro mixi-event-page (event)
1821   `(concat "/view_event.pl?id=" (mixi-event-id ,event)
1822            "&comm_id=" (mixi-community-id (mixi-event-community ,event))))
1823
1824 (defconst mixi-event-community-regexp
1825   "<td WIDTH=595 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b>\\[\\(.+\\)\\] ¥¤¥Ù¥ó¥È</b></td>")
1826 (defconst mixi-event-time-regexp
1827   "<td ROWSPAN=11 \\(BGCOLOR\\|bgcolor\\)=#FFD8B0 \\(ALIGN\\|align\\)=center \\(VALIGN\\|Valign\\)=top WIDTH=110>
1828 ?\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1829 ?\\([0-9]+\\):\\([0-9]+\\)</td>")
1830 (defconst mixi-event-title-regexp
1831   "<td bgcolor=#FFF4E0\\( width=410\\)?>&nbsp;\\([^<]+\\)</td>")
1832 (defconst mixi-event-owner-regexp
1833   "<td \\(BGCOLOR\\|bgcolor\\)=#FDF9F2>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>")
1834 (defconst mixi-event-owner-seceded-regexp
1835   "<td \\(BGCOLOR\\|bgcolor\\)=#FDF9F2>&nbsp;\\((mixi Âà²ñºÑ)\\)")
1836 (defconst mixi-event-date-regexp
1837   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>³«ºÅÆü»þ</td>
1838 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1839 &nbsp;\\(.+\\)
1840 </td>")
1841 (defconst mixi-event-place-regexp
1842   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>³«ºÅ¾ì½ê</td>
1843 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1844 &nbsp;\\(.+\\)
1845 </td>")
1846 (defconst mixi-event-detail-regexp
1847   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>¾ÜºÙ</td>
1848 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)><table BORDER=0 CELLSPACING=0 CELLPADDING=5><tr><td CLASS=h120>\\(.+\\)</td></tr></table></td>")
1849 (defconst mixi-event-limit-regexp
1850   "<td \\(BGCOLOR\\|bgcolor\\)=\"?#\\(FFFFFF\\|ffffff\\)\"? \\(ALIGN\\|align\\)=\"?center\"? NOWRAP>Ê罸´ü¸Â</td>
1851 ?<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1852 (defconst mixi-event-members-regexp
1853   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>»²²Ã¼Ô</td>
1854 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1855
1856 ?
1857 ?<table BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=100%>
1858 <tr>
1859
1860 ?<td>&nbsp;\\(.+\\)</td>")
1861
1862 (defun mixi-realize-event (event)
1863   "Realize a EVENT."
1864   ;; FIXME: Check a expiration of cache?
1865   (unless (mixi-object-realized-p event)
1866     (with-mixi-retrieve (mixi-event-page event)
1867       (if (re-search-forward mixi-event-community-regexp nil t)
1868           (mixi-community-set-name (mixi-event-community event)
1869                                    (match-string 1))
1870         (mixi-realization-error 'cannot-find-community event))
1871       (if (re-search-forward mixi-event-time-regexp nil t)
1872           (mixi-event-set-time
1873            event (encode-time 0 (string-to-number (match-string 8))
1874                               (string-to-number (match-string 7))
1875                               (string-to-number (match-string 6))
1876                               (string-to-number (match-string 5))
1877                               (string-to-number (match-string 4))))
1878         (mixi-realization-error 'cannot-find-time event))
1879       (if (re-search-forward mixi-event-title-regexp nil t)
1880           (mixi-event-set-title event (match-string 2))
1881         (mixi-realization-error 'cannot-find-title event))
1882       (if (re-search-forward mixi-event-owner-regexp nil t)
1883           (mixi-event-set-owner event
1884                                 (mixi-make-friend (match-string 2)
1885                                                   (match-string 3)))
1886         (if (re-search-forward mixi-event-owner-seceded-regexp nil t)
1887             (mixi-event-set-owner event
1888                                   (mixi-make-friend nil (match-string 2)))
1889           (mixi-realization-error 'cannot-find-owner event)))
1890       (if (re-search-forward mixi-event-date-regexp nil t)
1891           (mixi-event-set-date event (match-string 6))
1892         (mixi-realization-error 'cannot-find-date event))
1893       (if (re-search-forward mixi-event-place-regexp nil t)
1894           (mixi-event-set-place event (match-string 6))
1895         (mixi-realization-error 'cannot-find-place event))
1896       (if (re-search-forward mixi-event-detail-regexp nil t)
1897           (mixi-event-set-detail event (match-string 6))
1898         (mixi-realization-error 'cannot-find-detail event))
1899       (when (re-search-forward mixi-event-limit-regexp nil t)
1900         (mixi-event-set-limit
1901          event (encode-time 0 0 0 (string-to-number (match-string 8))
1902                             (string-to-number (match-string 7))
1903                             (string-to-number (match-string 6)))))
1904       (if (re-search-forward mixi-event-members-regexp nil t)
1905           (mixi-event-set-members event (match-string 6))
1906         (mixi-realization-error 'cannot-find-members event)))
1907     (mixi-object-touch event)))
1908
1909 (defun mixi-event-community (event)
1910   "Return the community of EVENT."
1911   (unless (mixi-event-p event)
1912     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1913   (aref (cdr event) 1))
1914
1915 (defun mixi-event-id (event)
1916   "Return the id of EVENT."
1917   (unless (mixi-event-p event)
1918     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1919   (aref (cdr event) 2))
1920
1921 (defun mixi-event-time (event)
1922   "Return the time of EVENT."
1923   (unless (mixi-event-p event)
1924     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1925   (mixi-realize-event event)
1926   (aref (cdr event) 3))
1927
1928 (defun mixi-event-title (event)
1929   "Return the title of EVENT."
1930   (unless (mixi-event-p event)
1931     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1932   (mixi-realize-event event)
1933   (aref (cdr event) 4))
1934
1935 (defun mixi-event-owner (event)
1936   "Return the owner of EVENT."
1937   (unless (mixi-event-p event)
1938     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1939   (mixi-realize-event event)
1940   (aref (cdr event) 5))
1941
1942 (defun mixi-event-date (event)
1943   "Return the date of EVENT."
1944   (unless (mixi-event-p event)
1945     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1946   (mixi-realize-event event)
1947   (aref (cdr event) 6))
1948
1949 (defun mixi-event-place (event)
1950   "Return the place of EVENT."
1951   (unless (mixi-event-p event)
1952     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1953   (mixi-realize-event event)
1954   (aref (cdr event) 7))
1955
1956 (defun mixi-event-detail (event)
1957   "Return the detail of EVENT."
1958   (unless (mixi-event-p event)
1959     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1960   (mixi-realize-event event)
1961   (aref (cdr event) 8))
1962
1963 (defun mixi-event-limit (event)
1964   "Return the limit of EVENT."
1965   (unless (mixi-event-p event)
1966     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1967   (mixi-realize-event event)
1968   (aref (cdr event) 9))
1969
1970 (defun mixi-event-members (event)
1971   "Return the members of EVENT."
1972   (unless (mixi-event-p event)
1973     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1974   (mixi-realize-event event)
1975   (aref (cdr event) 10))
1976
1977 (defun mixi-event-set-time (event time)
1978   "Set the time of EVENT."
1979   (unless (mixi-event-p event)
1980     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1981   (aset (cdr event) 3 time))
1982
1983 (defun mixi-event-set-title (event title)
1984   "Set the title of EVENT."
1985   (unless (mixi-event-p event)
1986     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1987   (aset (cdr event) 4 title))
1988
1989 (defun mixi-event-set-owner (event owner)
1990   "Set the owner of EVENT."
1991   (unless (mixi-event-p event)
1992     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1993   (unless (mixi-friend-p owner)
1994     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1995   (aset (cdr event) 5 owner))
1996
1997 (defun mixi-event-set-date (event date)
1998   "Set the date of EVENT."
1999   (unless (mixi-event-p event)
2000     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2001   (aset (cdr event) 6 date))
2002
2003 (defun mixi-event-set-place (event place)
2004   "Set the place of EVENT."
2005   (unless (mixi-event-p event)
2006     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2007   (aset (cdr event) 7 place))
2008
2009 (defun mixi-event-set-detail (event detail)
2010   "Set the detail of EVENT."
2011   (unless (mixi-event-p event)
2012     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2013   (aset (cdr event) 8 detail))
2014
2015 (defun mixi-event-set-limit (event limit)
2016   "Set the limit of EVENT."
2017   (unless (mixi-event-p event)
2018     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2019   (aset (cdr event) 9 limit))
2020
2021 (defun mixi-event-set-members (event members)
2022   "Set the members of EVENT."
2023   (unless (mixi-event-p event)
2024     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2025   (aset (cdr event) 10 members))
2026
2027 ;; Bbs object.
2028 (defconst mixi-bbs-list '(mixi-topic mixi-event))
2029
2030 (defmacro mixi-bbs-p (object)
2031   `(memq (mixi-object-class ,object) mixi-bbs-list))
2032
2033 (defun mixi-bbs-community (object)
2034   "Return the community of OBJECT."
2035   (unless (mixi-bbs-p object)
2036     (signal 'wrong-type-argument (list 'mixi-bbs-p object)))
2037   (let ((func (intern (concat mixi-object-prefix
2038                               (mixi-object-name object) "-community"))))
2039     (funcall func object)))
2040
2041 (defalias 'mixi-bbs-id 'mixi-object-id)
2042 (defalias 'mixi-bbs-time 'mixi-object-time)
2043 (defalias 'mixi-bbs-title 'mixi-object-title)
2044 (defalias 'mixi-bbs-owner 'mixi-object-owner)
2045 (defalias 'mixi-bbs-content 'mixi-object-content)
2046
2047 (defmacro mixi-bbs-list-page (community)
2048   `(concat "/list_bbs.pl?page=%d"
2049            "&id=" (mixi-community-id ,community)))
2050
2051 (defconst mixi-bbs-list-regexp
2052   "<a href=view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)")
2053
2054 (defun mixi-get-bbses (community &optional range)
2055   "Get bbese of COMMUNITY."
2056   (unless (mixi-community-p community)
2057     (signal 'wrong-type-argument (list 'mixi-community-p community)))
2058   (let ((items (mixi-get-matched-items (mixi-bbs-list-page community)
2059                                        mixi-bbs-list-regexp
2060                                        range)))
2061     (mapcar (lambda (item)
2062               (let ((name (nth 0 item)))
2063                 (when (string= name "bbs")
2064                   (setq name "topic"))
2065                 (let ((func (intern (concat "mixi-make-" name))))
2066                   (funcall func community (nth 1 item)))))
2067             items)))
2068
2069 (defmacro mixi-new-bbs-list-page ()
2070   `(concat "/new_bbs.pl?page=%d"))
2071
2072 (defconst mixi-new-bbs-list-regexp
2073   "<a href=\"view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
2074
2075 (defun mixi-get-new-bbses (&optional range)
2076   "Get new topics."
2077   (let ((items (mixi-get-matched-items (mixi-new-bbs-list-page)
2078                                        mixi-new-bbs-list-regexp
2079                                        range)))
2080     (mapcar (lambda (item)
2081               (let ((name (nth 0 item)))
2082                 (when (string= name "bbs")
2083                   (setq name "topic"))
2084                 (let ((func (intern (concat "mixi-make-" name))))
2085                   (funcall func (mixi-make-community (nth 2 item))
2086                            (nth 1 item)))))
2087             items)))
2088
2089 (defmacro mixi-search-bbs-list-page (keyword)
2090   `(concat "/search_topic.pl?page=%d&type=top&submit=search"
2091            "&keyword=" (mixi-url-encode-and-quote-percent-string ,keyword)
2092            "&community_id=0&category_id=0"))
2093
2094 (defconst mixi-search-bbs-list-regexp
2095   "<a href=\"view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)&comm_id=\\([0-9]+\\)\"><img src=http://img\\.mixi\\.jp/img/shbtn\\.gif ALT=¾ÜºÙ¤ò¸«¤ë BORDER=0 WIDTH=104 HEIGHT=19></a>")
2096
2097 ;; FIXME: Support community and category.
2098 (defun mixi-search-bbses (keyword &optional range)
2099   (let ((items (mixi-get-matched-items (mixi-search-bbs-list-page keyword)
2100                                        mixi-search-bbs-list-regexp
2101                                        range)))
2102     (mapcar (lambda (item)
2103               (let ((name (nth 0 item)))
2104                 (when (string= name "bbs")
2105                   (setq name "topic"))
2106                 (let ((func (intern (concat "mixi-make-" name))))
2107                   (funcall func (mixi-make-community (nth 2 item))
2108                            (nth 1 item)))))
2109             items)))
2110
2111 ;; Comment object.
2112 (defun mixi-make-comment (parent owner time content)
2113   "Return a comment object."
2114   (cons 'mixi-comment (vector parent owner time content)))
2115
2116 (defmacro mixi-comment-p (comment)
2117   `(eq (mixi-object-class ,comment) 'mixi-comment))
2118
2119 (defun mixi-comment-parent (comment)
2120   "Return the parent of COMMENT."
2121   (unless (mixi-comment-p comment)
2122     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2123   (aref (cdr comment) 0))
2124
2125 (defun mixi-comment-owner (comment)
2126   "Return the owner of COMMENT."
2127   (unless (mixi-comment-p comment)
2128     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2129   (aref (cdr comment) 1))
2130
2131 (defun mixi-comment-time (comment)
2132   "Return the time of COMMENT."
2133   (unless (mixi-comment-p comment)
2134     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2135   (aref (cdr comment) 2))
2136
2137 (defun mixi-comment-content (comment)
2138   "Return the content of COMMENT."
2139   (unless (mixi-comment-p comment)
2140     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2141   (aref (cdr comment) 3))
2142
2143 (defun mixi-diary-comment-list-page (diary)
2144   (concat "/view_diary.pl?full=1"
2145           "&id=" (mixi-diary-id diary)
2146           "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
2147
2148 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2149 (defconst mixi-diary-comment-list-regexp
2150 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
2151 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
2152 <input type=checkbox name=comment_id value=\".+\">
2153 \\|\\)
2154 </td>
2155 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
2156 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
2157 <tr>
2158 \\(<td>\\)
2159 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2160
2161 \\(<font color=\"#f2ddb7\">|</font> <a href=[^>]+>ºï½ü</a>
2162
2163 \\|\\)</td>
2164 </tr>
2165 </table>
2166 </td>
2167 </tr>
2168 <!-- [^ ]+ : start -->
2169 <tr>
2170 <td bgcolor=\"#ffffff\">
2171 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
2172 <tr>
2173 <td CLASS=h12>
2174 \\(.+\\)
2175 </td></tr></table>")
2176
2177 (defun mixi-topic-comment-list-page (topic)
2178   (concat "/view_bbs.pl?page=all"
2179           "&id=" (mixi-topic-id topic)
2180           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
2181
2182 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2183 (defconst mixi-topic-comment-list-regexp
2184   "<tr valign=\"top\">
2185 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
2186 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
2187 \\([0-9]+\\):\\([0-9]+\\)<br>
2188 \\(<input type=\"checkbox\" name=\"comment_id\" value=\".+\">
2189 \\|\\)</td>
2190 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
2191 <b>[^<]+</b>:</font>&nbsp;
2192 \\(
2193 \\|\\) *<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2194
2195 ?\\(
2196
2197 \\|<font color=\"#f2ddb7\">|&nbsp;</font><a href=\"delete_bbs_comment\\.pl\\?id=[0-9]+&comm_id=[0-9]+&comment_id=[0-9]+\">ºï½ü</a>
2198 \\|\\)</td>
2199 </tr>
2200 <tr>
2201 <td bgcolor=\"#ffffff\" align=\"center\">
2202 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
2203 <tr>
2204 <td class=\"h120\">
2205
2206 \\(.+\\)
2207 </td>
2208 </tr>
2209 </table>
2210 </td>
2211 </tr>")
2212
2213 (defun mixi-event-comment-list-page (event)
2214   (concat "/view_event.pl?page=all"
2215           "&id=" (mixi-event-id event)
2216           "&comm_id=" (mixi-community-id (mixi-event-community event))))
2217
2218 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2219 (defconst mixi-event-comment-list-regexp
2220   "<tr>
2221 <td ROWSPAN=2 ALIGN=center BGCOLOR=#F2DDB7 WIDTH=110>
2222 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
2223 \\([0-9]+\\):\\([0-9]+\\)<br>
2224 \\(</td>\\)
2225 \\(<td BGCOLOR=#FDF9F2>\\)
2226 <font COLOR=#F8A448><b>[^<]+</b> :</font>
2227 <a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2228
2229 \\(<font COLOR=#F2DDB7>|</font>
2230 <a href=\"delete_bbs_comment\\.pl\\?id=[0-9]+&comm_id=[0-9]+&comment_id=[0-9]+&type=event\">ºï½ü</a>
2231
2232 \\|\\)</td>
2233 </tr>
2234 <tr>
2235 <td ALIGN=center BGCOLOR=#FFFFFF>
2236 <table BORDER=0 CELLSPACING=0 CELLPADDING=5 WIDTH=500>
2237 <tr><td CLASS=h120>\\(.+\\)</td></tr>
2238 </table>
2239 </td>
2240 </tr>")
2241
2242 (defun mixi-get-comments (parent &optional range)
2243   "Get comments of PARENT."
2244   (unless (mixi-object-p parent)
2245     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
2246   (let* ((name (mixi-object-name parent))
2247          (list-page (intern (concat mixi-object-prefix name
2248                                     "-comment-list-page")))
2249          (regexp (eval (intern (concat mixi-object-prefix name
2250                                        "-comment-list-regexp")))))
2251     (let ((items (mixi-get-matched-items
2252                   (funcall list-page parent) regexp)))
2253       (let (list)
2254         (catch 'stop
2255           (mapc (lambda (item)
2256                   (when (and (numberp range)
2257                              (>= (length list) range))
2258                     (throw 'stop nil))
2259                   (setq list (cons item list)))
2260                 (reverse items)))
2261         (setq items (reverse list)))
2262       (mapcar (lambda (item)
2263                 (mixi-make-comment parent (mixi-make-friend
2264                                            (nth 7 item) (nth 8 item))
2265                                    (encode-time
2266                                     0
2267                                     (string-to-number (nth 4 item))
2268                                     (string-to-number (nth 3 item))
2269                                     (string-to-number (nth 2 item))
2270                                     (string-to-number (nth 1 item))
2271                                     (string-to-number (nth 0 item)))
2272                                    (nth 10 item)))
2273               items))))
2274
2275 (defmacro mixi-new-comment-list-page ()
2276   `(concat "/new_comment.pl?page=%d"))
2277
2278 (defconst mixi-new-comment-list-regexp
2279   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
2280
2281 (defun mixi-get-new-comments (&optional range)
2282   "Get new comments."
2283   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
2284                                        mixi-new-comment-list-regexp
2285                                        range)))
2286     (mapcar (lambda (item)
2287               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
2288             items)))
2289
2290 (defun mixi-post-diary-comment-page (diary)
2291   (concat "/add_comment.pl?&diary_id=" (mixi-diary-id diary)))
2292
2293 (defun mixi-post-topic-comment-page (topic)
2294   (concat "/add_bbs_comment.pl?id=" (mixi-topic-id topic)
2295           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
2296
2297 (defun mixi-post-event-comment-page (event)
2298   (concat "/add_event_comment.pl?id=" (mixi-event-id event)
2299           "&comm_id=" (mixi-community-id (mixi-event-community event))))
2300
2301 ;; FIXME: Support photos.
2302 (defun mixi-post-comment (parent content)
2303   "Post a comment to PARENT."
2304   (unless (mixi-object-p parent)
2305     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
2306   (unless (stringp content)
2307     (signal 'wrong-type-argument (list 'stringp content)))
2308   (let* ((name (mixi-object-name parent))
2309          (page (intern (concat mixi-object-prefix "post-" name
2310                                "-comment-page")))
2311          fields post-key)
2312     (if (mixi-diary-p parent)
2313         (setq fields
2314               `(("owner_id" . ,(mixi-friend-id (mixi-diary-owner parent)))
2315                 ("comment_body" . ,content)))
2316       (setq fields `(("comment" . ,content))))
2317     (with-mixi-post-form (funcall page parent) fields
2318       (if (re-search-forward mixi-post-key-regexp nil t)
2319           (setq post-key (match-string 1))
2320         (mixi-post-error 'cannot-find-key parent)))
2321     (if (mixi-diary-p parent)
2322         (setq fields
2323               `(("post_key" . ,post-key)
2324                 ("owner_id" . ,(mixi-friend-id (mixi-diary-owner parent)))
2325                 ("comment_body" . ,content)
2326                 ("submit" . "confirm")))
2327       (setq fields `(("post_key" . ,post-key)
2328                      ("comment" . ,content)
2329                      ("submit" . "confirm"))))
2330     (with-mixi-post-form (funcall page parent) fields
2331       (unless (re-search-forward mixi-post-succeed-regexp nil t)
2332         (mixi-post-error 'cannot-find-succeed parent)))))
2333
2334 ;; Message object.
2335 (defconst mixi-message-box-list '(inbox outbox savebox thrash)) ; thrash?
2336
2337 (defmacro mixi-message-box-p (box)
2338   `(memq ,box mixi-message-box-list))
2339
2340 (defun mixi-message-box-name (box)
2341   "Return the name of BOX."
2342   (unless (mixi-message-box-p box)
2343     (signal 'wrong-type-argument (list 'mixi-message-box-p box)))
2344   (symbol-name box))
2345
2346 (defvar mixi-message-cache (make-hash-table :test 'equal))
2347 (defun mixi-make-message (id box &optional owner title time content)
2348   "Return a message object."
2349   (mixi-make-cache (list id box)
2350                    (cons 'mixi-message (vector nil id box owner title time
2351                                                content))
2352                    mixi-message-cache))
2353
2354 (defconst mixi-message-url-regexp
2355   "/view_message\\.pl\\?id=\\([a-z0-9]+\\)&box=\\([a-z]+\\)")
2356
2357 (defun mixi-make-message-from-url (url)
2358   "Return a message object from URL."
2359   (when (string-match mixi-message-url-regexp url)
2360     (let ((id (match-string 1 url))
2361           (box (match-string 2 url)))
2362       (mixi-make-message id box))))
2363
2364 (defmacro mixi-message-p (message)
2365   `(eq (mixi-object-class ,message) 'mixi-message))
2366
2367 (defmacro mixi-message-page (message)
2368   `(concat "/view_message.pl?id=" (mixi-message-id ,message)
2369            "&box=" (mixi-message-box ,message)))
2370
2371 (defconst mixi-message-owner-regexp
2372   "<font COLOR=#996600>\\(º¹½Ð¿Í\\|°¸&nbsp;Àè\\)</font>&nbsp;:&nbsp;<a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)\\(</a>\\|</td>\\)")
2373 (defconst mixi-message-time-regexp
2374 "<font COLOR=#996600>Æü\\(¡¡\\|&nbsp;\\)ÉÕ</font>&nbsp;:&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\)»þ\\([0-9]+\\)ʬ&nbsp;&nbsp;")
2375 (defconst mixi-message-title-regexp
2376 "<font COLOR=#996600>·ï\\(¡¡\\|&nbsp;\\)̾</font>&nbsp;:&nbsp;\\(.+\\)\n?</td>")
2377 (defconst mixi-message-content-regexp
2378   "<tr><td CLASS=h120>\\(.+\\)</td></tr>")
2379
2380 (defun mixi-realize-message (message)
2381   "Realize a MESSAGE."
2382   (unless (mixi-object-realized-p message)
2383     (with-mixi-retrieve (mixi-message-page message)
2384       (if (re-search-forward mixi-message-owner-regexp nil t)
2385           (mixi-message-set-owner message
2386                                   (mixi-make-friend (match-string 2)
2387                                                     (match-string 3)))
2388         (mixi-realization-error 'cannot-find-owner message))
2389       (if (re-search-forward mixi-message-time-regexp nil t)
2390           (mixi-message-set-time
2391            message (encode-time 0 (string-to-number (match-string 6))
2392                                 (string-to-number (match-string 5))
2393                                 (string-to-number (match-string 4))
2394                                 (string-to-number (match-string 3))
2395                                 (string-to-number (match-string 2))))
2396         (mixi-realization-error 'cannot-find-time message))
2397       (if (re-search-forward mixi-message-title-regexp nil t)
2398           (mixi-message-set-title message (match-string 2))
2399         (mixi-realization-error 'cannot-find-title message))
2400       (if (re-search-forward mixi-message-content-regexp nil t)
2401           (mixi-message-set-content message (match-string 1))
2402         (mixi-realization-error 'cannot-find-content message)))
2403     (mixi-object-touch message)))
2404
2405 (defun mixi-message-id (message)
2406   "Return the id of MESSAGE."
2407   (unless (mixi-message-p message)
2408     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2409   (aref (cdr message) 1))
2410
2411 (defun mixi-message-box (message)
2412   "Return the box of MESSAGE."
2413   (unless (mixi-message-p message)
2414     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2415   (aref (cdr message) 2))
2416
2417 (defun mixi-message-owner (message)
2418   "Return the owner of MESSAGE."
2419   (unless (mixi-message-p message)
2420     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2421   (mixi-realize-message message)
2422   (aref (cdr message) 3))
2423
2424 (defun mixi-message-title (message)
2425   "Return the title of MESSAGE."
2426   (unless (mixi-message-p message)
2427     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2428   (mixi-realize-message message)
2429   (aref (cdr message) 4))
2430
2431 (defun mixi-message-time (message)
2432   "Return the date of MESSAGE."
2433   (unless (mixi-message-p message)
2434     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2435   (mixi-realize-message message)
2436   (aref (cdr message) 5))
2437
2438 (defun mixi-message-content (message)
2439   "Return the content of MESSAGE."
2440   (unless (mixi-message-p message)
2441     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2442   (mixi-realize-message message)
2443   (aref (cdr message) 6))
2444
2445 (defun mixi-message-set-owner (message owner)
2446   "Set the owner of MESSAGE."
2447   (unless (mixi-message-p message)
2448     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2449   (aset (cdr message) 3 owner))
2450
2451 (defun mixi-message-set-title (message title)
2452   "Set the title of MESSAGE."
2453   (unless (mixi-message-p message)
2454     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2455   (aset (cdr message) 4 title))
2456
2457 (defun mixi-message-set-time (message time)
2458   "Set the date of MESSAGE."
2459   (unless (mixi-message-p message)
2460     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2461   (aset (cdr message) 5 time))
2462
2463 (defun mixi-message-set-content (message content)
2464   "Set the content of MESSAGE."
2465   (unless (mixi-message-p message)
2466     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2467   (aset (cdr message) 6 content))
2468
2469 (defmacro mixi-message-list-page (&optional box)
2470   `(concat "/list_message.pl?page=%d"
2471            (when ,box (concat "&box=" ,box))))
2472
2473 (defconst mixi-message-list-regexp
2474   "<td><a HREF=\"view_message\\.pl\\?id=\\(.+\\)&box=\\(.+\\)\">")
2475
2476 (defun mixi-get-messages (&rest box-or-range)
2477   "Get messages of BOX."
2478   (when (> (length box-or-range) 2)
2479     (signal 'wrong-number-of-arguments
2480             (list 'mixi-get-messages (length box-or-range))))
2481   (let ((box (nth 0 box-or-range))
2482         (range (nth 1 box-or-range)))
2483     (when (or (not (mixi-message-box-p box))
2484               (mixi-message-box-p range))
2485       (setq box (nth 1 box-or-range))
2486       (setq range (nth 0 box-or-range)))
2487     (let ((items (mixi-get-matched-items
2488                   (mixi-message-list-page
2489                    (when box (mixi-message-box-name box)))
2490                   mixi-message-list-regexp
2491                   range)))
2492       (mapcar (lambda (item)
2493                 (mixi-make-message (nth 0 item) (nth 1 item)))
2494               items))))
2495
2496 (defmacro mixi-post-message-page (friend)
2497   `(concat "/send_message.pl?id=" (mixi-friend-id friend)))
2498
2499 (defconst mixi-post-message-key-regexp
2500   "<input name=post_key type=hidden value=\\([a-z0-9]+\\)>")
2501
2502 (defconst mixi-post-message-succeed-regexp
2503   "<b>Á÷¿®´°Î»</b>¤·¤Þ¤·¤¿¡£")
2504
2505 (defun mixi-post-message (friend title content)
2506   "Post a message to FRIEND."
2507   (unless (mixi-friend-p friend)
2508     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
2509   (unless (stringp title)
2510     (signal 'wrong-type-argument (list 'stringp title)))
2511   (unless (stringp content)
2512     (signal 'wrong-type-argument (list 'stringp content)))
2513   (let ((fields `(("subject" . ,title)
2514                   ("body" . ,content)
2515                   ("submit" . "main")))
2516         post-key)
2517     (with-mixi-post-form (mixi-post-message-page friend) fields
2518       (if (re-search-forward mixi-post-message-key-regexp nil t)
2519           (setq post-key (match-string 1))
2520         (mixi-post-error 'cannot-find-key friend)))
2521     (setq fields `(("post_key" . ,post-key)
2522                    ("subject" . ,title)
2523                    ("body" . ,content)
2524                    ("yes" . "¡¡Á÷¡¡¿®¡¡")
2525                    ("submit" . "confirm")))
2526     (with-mixi-post-form (mixi-post-message-page friend) fields
2527       (unless (re-search-forward mixi-post-message-succeed-regexp nil t)
2528         (mixi-post-error 'cannot-find-succeed friend)))))
2529
2530 ;; Introduction object.
2531 (defun mixi-make-introduction (parent owner content)
2532   "Return a introduction object."
2533   (cons 'mixi-introduction (vector parent owner content)))
2534
2535 (defmacro mixi-introduction-p (introduction)
2536   `(eq (mixi-object-class ,introduction) 'mixi-introduction))
2537
2538 (defun mixi-introduction-parent (introduction)
2539   "Return the parent of INTRODUCTION."
2540   (unless (mixi-introduction-p introduction)
2541     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2542   (aref (cdr introduction) 0))
2543
2544 (defun mixi-introduction-owner (introduction)
2545   "Return the owner of INTRODUCTION."
2546   (unless (mixi-introduction-p introduction)
2547     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2548   (aref (cdr introduction) 1))
2549
2550 (defun mixi-introduction-content (introduction)
2551   "Return the content of INTRODUCTION."
2552   (unless (mixi-introduction-p introduction)
2553     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2554   (aref (cdr introduction) 3))
2555
2556 (defmacro mixi-introduction-list-page (&optional friend)
2557   `(concat "/show_intro.pl?page=%d"
2558            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
2559
2560 (defconst mixi-introduction-list-regexp
2561   "<tr bgcolor=#FFFFFF>
2562 <td WIDTH=150 background=http://img\\.mixi\\.jp/img/bg_line\\.gif align=\"center\"><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\"><img src=\".+\" border=0><br>
2563 \\(.*\\)</td></a>
2564
2565 <td WIDTH=480>
2566 \\(´Ø·¸¡§.+<br>
2567
2568
2569 \\(\\(.\\|\n<br>\\)+\\)\\|
2570 \\(\\(.\\|\n<br>\\)+\\)\\)
2571
2572
2573
2574
2575 </td>
2576 </tr>")
2577 (defconst mixi-my-introduction-list-regexp
2578   "<tr bgcolor=#FFFFFF>
2579 <td WIDTH=150 background=http://img\\.mixi\\.jp/img/bg_line\\.gif align=\"center\"><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\"><img src=\".+\" border=0><br>
2580 \\(.*\\)</td></a>
2581
2582
2583 <td WIDTH=480>
2584 \\(´Ø·¸¡§.+<br>
2585
2586
2587 \\(\\(.\\|\n<br>\\)+\\)\\|
2588 \\(\\(.\\|\n<br>\\)+\\)\\)
2589
2590
2591 <br>
2592 <a href=\"edit_intro\\.pl\\?id=\\1&type=edit\">¤³¤Îͧ¿Í¤ò¾Ò²ð¤¹¤ë</a>
2593
2594
2595 <BR>
2596 <a href=\"delete_intro\\.pl\\?id=\\1\">ºï½ü</a>
2597
2598 </td>
2599 </tr>")
2600
2601 (defun mixi-get-introductions (&rest friend-or-range)
2602   "Get introductions of FRIEND."
2603   (when (> (length friend-or-range) 2)
2604     (signal 'wrong-number-of-arguments
2605             (list 'mixi-get-introduction (length friend-or-range))))
2606   (let ((friend (nth 0 friend-or-range))
2607         (range (nth 1 friend-or-range)))
2608     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
2609       (setq friend (nth 1 friend-or-range))
2610       (setq range (nth 0 friend-or-range)))
2611     (unless (or (null friend) (mixi-friend-p friend))
2612       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
2613     (let* ((regexp (if friend mixi-introduction-list-regexp
2614                      mixi-my-introduction-list-regexp))
2615            (items (mixi-get-matched-items (mixi-introduction-list-page friend)
2616                                           regexp
2617                                           range)))
2618       (mapcar (lambda (item)
2619                 (mixi-make-introduction (or friend (mixi-make-me))
2620                                         (mixi-make-friend (nth 0 item)
2621                                                           (nth 1 item))
2622                                         (nth 2 item)))
2623               items))))
2624
2625 ;; News object.
2626 (defvar mixi-news-cache (make-hash-table :test 'equal))
2627 (defun mixi-make-news (media-id id &optional media time title content)
2628   "Return a news object."
2629   (mixi-make-cache (list media-id id)
2630                    (cons 'mixi-news (vector nil media-id id media time title
2631                                             content))
2632                    mixi-news-cache))
2633
2634 (defconst mixi-news-url-regexp
2635   "/view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)")
2636
2637 (defun mixi-make-news-from-url (url)
2638   "Return a news object from URL."
2639   (when (string-match mixi-news-url-regexp url)
2640     (let ((id (match-string 1 url))
2641           (media-id (match-string 2 url)))
2642       (mixi-make-news media-id id))))
2643
2644 (defmacro mixi-news-p (news)
2645   `(eq (mixi-object-class ,news) 'mixi-news))
2646
2647 (defmacro mixi-news-page (news)
2648   `(concat "http://news.mixi.jp/view_news.pl?id=" (mixi-news-id ,news)
2649            "&media_id=" (mixi-news-media-id ,news)))
2650
2651 (defconst mixi-news-title-regexp
2652   "<td HEIGHT=\"46\" STYLE=\"font-weight: bold;font-size: 14px;\" CLASS=\"h130\">\\(.+\\)</td>")
2653 (defconst mixi-news-media-time-regexp
2654   "<td COLSPAN=\"2\" ALIGN=\"right\">(\\(.+\\)&nbsp;-&nbsp;\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\))</td></tr>")
2655 (defconst mixi-news-content-regexp
2656   "<td CLASS=\"h150\">
2657
2658 \\(.+\\)
2659
2660 ?
2661
2662 \\(</td>\\|<br>\\)")
2663
2664 (defun mixi-realize-news (news)
2665   "Realize a NEWS."
2666   ;; FIXME: Check a expiration of cache?
2667   (unless (mixi-object-realized-p news)
2668     (with-mixi-retrieve (mixi-news-page news)
2669       (if (re-search-forward mixi-news-title-regexp nil t)
2670           (mixi-news-set-title news (match-string 1))
2671         (mixi-realization-error 'cannot-find-title news))
2672       (if (re-search-forward mixi-news-media-time-regexp nil t)
2673           (progn
2674             (mixi-news-set-media news (match-string 1))
2675             (let ((year (nth 5 (decode-time (current-time))))
2676                   (month (nth 4 (decode-time (current-time))))
2677                   (month-of-item (string-to-number (match-string 2))))
2678               (when (> month-of-item month)
2679                 (decf year))
2680               (mixi-news-set-time
2681                news (encode-time 0 (string-to-number (match-string 5))
2682                                  (string-to-number (match-string 4))
2683                                  (string-to-number (match-string 3))
2684                                  month year))))
2685         (mixi-realization-error 'cannot-find-media-time news))
2686       (if (re-search-forward mixi-news-content-regexp nil t)
2687           (mixi-news-set-content news (match-string 1))
2688         (mixi-realization-error 'cannot-find-content news)))
2689     (mixi-object-touch news)))
2690
2691 (defun mixi-news-media-id (news)
2692   "Return the media-id of NEWS."
2693   (unless (mixi-news-p news)
2694     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2695   (aref (cdr news) 1))
2696
2697 (defun mixi-news-id (news)
2698   "Return the id of NEWS."
2699   (unless (mixi-news-p news)
2700     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2701   (aref (cdr news) 2))
2702
2703 (defun mixi-news-media (news)
2704   "Return the media of NEWS."
2705   (unless (mixi-news-p news)
2706     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2707   (unless (aref (cdr news) 3)
2708     (mixi-realize-news news))
2709   (aref (cdr news) 3))
2710
2711 (defun mixi-news-time (news)
2712   "Return the time of NEWS."
2713   (unless (mixi-news-p news)
2714     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2715   (unless (aref (cdr news) 4)
2716     (mixi-realize-news news))
2717   (aref (cdr news) 4))
2718
2719 (defun mixi-news-title (news)
2720   "Return the title of NEWS."
2721   (unless (mixi-news-p news)
2722     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2723   (unless (aref (cdr news) 5)
2724     (mixi-realize-news news))
2725   (aref (cdr news) 5))
2726
2727 (defun mixi-news-content (news)
2728   "Return the content of NEWS."
2729   (unless (mixi-news-p news)
2730     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2731   (mixi-realize-news news)
2732   (aref (cdr news) 6))
2733
2734 (defun mixi-news-set-media (news media)
2735   "Set the media of NEWS."
2736   (unless (mixi-news-p news)
2737     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2738   (aset (cdr news) 3 media))
2739
2740 (defun mixi-news-set-time (news time)
2741   "Set the time of NEWS."
2742   (unless (mixi-news-p news)
2743     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2744   (aset (cdr news) 4 time))
2745
2746 (defun mixi-news-set-title (news title)
2747   "Set the title of NEWS."
2748   (unless (mixi-news-p news)
2749     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2750   (aset (cdr news) 5 title))
2751
2752 (defun mixi-news-set-content (news content)
2753   "Set the content of NEWS."
2754   (unless (mixi-news-p news)
2755     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2756   (aset (cdr news) 6 content))
2757
2758 (defconst mixi-news-category-list '(domestic politics economy area abroad
2759                                              sports entertainment IT))
2760
2761 (defmacro mixi-news-category-p (category)
2762   `(memq ,category mixi-news-category-list))
2763
2764 (defun mixi-news-category-id (category)
2765   "Return the id of CATEGORY."
2766   (unless (mixi-news-category-p category)
2767     (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
2768   (number-to-string
2769    (1+ (- (length mixi-news-category-list)
2770           (length (memq category mixi-news-category-list))))))
2771
2772 (defconst mixi-news-sort-list '(newest pickup))
2773
2774 (defmacro mixi-news-sort-p (sort)
2775   `(memq ,sort mixi-news-sort-list))
2776
2777 (defun mixi-news-sort-id (sort)
2778   "Return the id of SORT."
2779   (unless (mixi-news-sort-p sort)
2780     (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
2781   (number-to-string
2782    (- (length mixi-news-sort-list)
2783       (length (memq sort mixi-news-sort-list)))))
2784
2785 (defmacro mixi-news-list-page (category sort)
2786   `(concat "http://news.mixi.jp/list_news_category.pl?page=%d"
2787            "&sort=" (mixi-news-sort-id ,sort)
2788            "&id=" (mixi-news-category-id ,category)
2789            "&type=bn"))
2790
2791 (defconst mixi-news-list-regexp
2792   "<tr bgcolor=\"\\(#FCF5EB\\|#FFFFFF\\)\">
2793 <td WIDTH=\"1%\" valign=top CLASS=\"h120\">¡¦</td>
2794 <td WIDTH=\"97%\" CLASS=\"h120\"><A HREF=\"view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)\"class=\"new_link\">\\(.+\\)</A>
2795 \\(<IMG SRC=\"http://img\\.mixi\\.jp/img/news_camera3\\.gif\" WIDTH=\"11\" HEIGHT=\"12\">\\|\\)
2796
2797 </td>
2798 <td WIDTH=\"1%\" nowrap CLASS=\"f08\"><A HREF=\"list_news_media\\.pl\\?id=[0-9]+\">\\(.+\\)</A></td>
2799 <td WIDTH=\"1%\" nowrap CLASS=\"f08\">\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td></tr>")
2800
2801 (defun mixi-get-news (category sort &optional range)
2802   "Get news of CATEGORY and SORT."
2803   (unless (mixi-news-category-p category)
2804     (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
2805   (unless (mixi-news-sort-p sort)
2806     (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
2807   (let ((items (mixi-get-matched-items (mixi-news-list-page category sort)
2808                                        mixi-news-list-regexp
2809                                        range))
2810         (year (nth 5 (decode-time (current-time))))
2811         (month (nth 4 (decode-time (current-time)))))
2812     (mapcar (lambda (item)
2813               (let ((month-of-item (string-to-number (nth 6 item))))
2814                 (when (> month-of-item month)
2815                   (decf year))
2816                 (setq month month-of-item)
2817                 (mixi-make-news (nth 2 item) (nth 1 item) (nth 5 item)
2818                                 (encode-time
2819                                  0 (string-to-number (nth 9 item))
2820                                  (string-to-number (nth 8 item))
2821                                  (string-to-number (nth 7 item))
2822                                  month year)
2823                                 (nth 3 item))))
2824             items)))
2825
2826 (provide 'mixi)
2827
2828 ;;; mixi.el ends here