* mixi.el (mixi-search-community-list-page): New macro.
[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-get-comments
40 ;;  * mixi-get-new-comments
41 ;;  * mixi-get-messages
42 ;;  * mixi-get-introductions
43 ;; 
44 ;; Utilities:
45 ;;
46 ;;  * mixi-remove-markup
47
48 ;; Example:
49 ;;
50 ;; Display newest 3 diaries like a mail format.
51 ;;
52 ;; (let ((range 3)
53 ;;       (buffer (get-buffer-create "*temp*"))
54 ;;       (format "%Y/%m/%d %H:%M"))
55 ;;   (pop-to-buffer buffer)
56 ;;   (mapc (lambda (diary)
57 ;;        (let ((subject (mixi-diary-title diary))
58 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
59 ;;              (date (format-time-string format (mixi-diary-time diary)))
60 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
61 ;;          (insert "From: " from "\n"
62 ;;                  "Subject: " subject "\n"
63 ;;                  "Date: " date "\n\n"
64 ;;                  body "\n\n")))
65 ;;      (mixi-get-new-diaries range))
66 ;;   (set-buffer-modified-p nil)
67 ;;   (setq buffer-read-only t)
68 ;;   (goto-char (point-min)))
69 ;;
70 ;; Display newest 3 diaries including newest 3 comments like a mail format.
71 ;; Comments are displayed like a reply mail.
72 ;;
73 ;; (let ((range 3)
74 ;;       (buffer (get-buffer-create "*temp*"))
75 ;;       (format "%Y/%m/%d %H:%M"))
76 ;;   (pop-to-buffer buffer)
77 ;;   (mapc (lambda (diary)
78 ;;        (let ((subject (mixi-diary-title diary))
79 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
80 ;;              (date (format-time-string format (mixi-diary-time diary)))
81 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
82 ;;          (insert "From: " from "\n"
83 ;;                  "Subject: " subject "\n"
84 ;;                  "Date: " date "\n\n"
85 ;;                  body "\n\n")
86 ;;          (mapc (lambda (comment)
87 ;;                  (let ((from (mixi-friend-nick
88 ;;                               (mixi-comment-owner comment)))
89 ;;                        (subject (concat "Re: " subject))
90 ;;                        (date (format-time-string
91 ;;                               format (mixi-comment-time comment)))
92 ;;                        (body (mixi-remove-markup
93 ;;                               (mixi-comment-content comment))))
94 ;;                    (insert "From: " from "\n"
95 ;;                            "Subject: " subject "\n"
96 ;;                            "Date: " date "\n\n"
97 ;;                            body "\n\n")))
98 ;;                (mixi-get-comments diary range))))
99 ;;      (mixi-get-new-diaries range))
100 ;;   (set-buffer-modified-p nil)
101 ;;   (setq buffer-read-only t)
102 ;;   (goto-char (point-min)))
103
104 ;; Bug reports:
105 ;;
106 ;; If you have bug reports and/or suggestions for improvement, please
107 ;; send them via <URL:http://mixi.jp/view_community.pl?id=1596390>.
108
109 ;;; Code:
110
111 (eval-when-compile (require 'cl))
112
113 (defgroup mixi nil
114   "API library for accessing to mixi."
115   :group 'hypermedia)
116
117 (defcustom mixi-url "http://mixi.jp"
118   "*The URL of mixi."
119   :type 'string
120   :group 'mixi)
121
122 (defcustom mixi-coding-system 'euc-jp
123   "*Coding system for mixi."
124   :type 'coding-system
125   :group 'mixi)
126
127 (defcustom mixi-curl-program "curl"
128   "*The program name of `curl'."
129   :type 'file
130   :group 'mixi)
131
132 (defcustom mixi-curl-cookie-file (expand-file-name "~/.mixi-cookies.txt")
133   "*The location of cookie file created by `curl'."
134   :type 'file
135   :group 'mixi)
136
137 (defcustom mixi-retrieve-function
138   (or (condition-case nil
139           (progn
140             (require 'url)
141             (if (fboundp 'url-retrieve-synchronously)
142                 'mixi-url-retrieve))
143         (error))
144       (condition-case nil
145           (progn
146             (require 'w3m)
147             'mixi-w3m-retrieve)
148         (error))
149       (if (and (fboundp 'executable-find)
150                (executable-find mixi-curl-program))
151           'mixi-curl-retrieve)
152       (error "Can't set `mixi-retrieve-function'"))
153   "*The function for retrieving."
154   :type '(radio (const :tag "Using url" mixi-url-retrieve)
155                 (const :tag "Using w3m" mixi-w3m-retrieve)
156                 (const :tag "Using curl" mixi-curl-retrieve)
157                 (function :format "Other function: %v\n" :size 0))
158   :group 'mixi)
159
160 (defcustom mixi-default-email nil
161   "*Default E-mail address that is used to login automatically."
162   :type '(radio (string :tag "E-mail address")
163                 (const :tag "Asked when it is necessary" nil))
164   :group 'mixi)
165
166 (defcustom mixi-default-password nil
167   "*Default password that is used to login automatically."
168   :type '(radio (string :tag "Password")
169                 (const :tag "Asked when it is necessary" nil))
170   :group 'mixi)
171
172 (defcustom mixi-accept-adult-contents t
173   "*If non-nil, accept to access to the adult contents."
174   :type 'boolean
175   :group 'mixi)
176
177 (defcustom mixi-continuously-access-interval 4.0
178   "*Time interval between each mixi access.
179 Increase this value when unexpected error frequently occurs."
180   :type 'number
181   :group 'mixi)
182
183 (defcustom mixi-cache-expires nil
184   "*Seconds for expiration of a cached object."
185   :type '(radio (integer :tag "Expired seconds")
186                 (const :tag "Don't expire" nil)
187                 (const :tag "Don't cache" t))
188   :group 'mixi)
189
190 ;; FIXME: Not implemented.
191 (defcustom mixi-cache-use-file t
192   "*If non-nil, caches are saved to files."
193   :type 'boolean
194   :group 'mixi)
195
196 (defcustom mixi-cache-directory (expand-file-name "~/.mixi")
197   "*Where to look for cache files."
198   :type 'directory
199   :group 'mixi)
200
201 (defvar mixi-me nil)
202
203 ;; Utilities.
204 (defmacro mixi-message (&rest strings)
205   `(concat "[mixi] " ,@strings))
206
207 (defconst mixi-message-adult-contents
208   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
209 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
210 (defconst mixi-message-continuously-accessing
211   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
212 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
213 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
214 (defconst mixi-warning-continuously-accessing
215   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
216 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
217 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
218
219 (defmacro mixi-retrieve (url &optional post-data)
220   `(funcall mixi-retrieve-function ,url ,post-data))
221
222 (defun mixi-parse-buffer (url buffer &optional post-data)
223   (when (string-match mixi-message-adult-contents buffer)
224     (if mixi-accept-adult-contents
225         (setq buffer (mixi-retrieve url "submit=agree"))
226       (setq buffer (mixi-retrieve (concat url "?")))))
227   (when (string-match mixi-warning-continuously-accessing buffer)
228     (error (mixi-message "Access denied.  Please wait a while and increase "
229                          "the value of `mixi-continuously-access-interval'.")))
230   (if (not (string-match mixi-message-continuously-accessing buffer))
231       buffer
232     (message (mixi-message "Waiting for continuously accessing..."))
233     (sit-for mixi-continuously-access-interval)
234     (mixi-retrieve url post-data)))
235
236 (defmacro mixi-expand-url (url)
237   `(if (string-match (concat "^" mixi-url) ,url)
238        ,url
239      (concat mixi-url ,url)))
240
241 (defun mixi-url-retrieve (url &optional post-data)
242   "Retrieve the URL and return gotten strings."
243   (if post-data
244       (progn
245         (setq url-request-method "POST")
246         (setq url-request-data post-data))
247     (setq url-request-method "GET")
248     (setq url-request-data nil))
249   (let* ((url (mixi-expand-url url))
250          (buffer (url-retrieve-synchronously url))
251          ret)
252     (unless (bufferp buffer)
253       (error (mixi-message "Cannot retrieve")))
254     (with-current-buffer buffer
255       (goto-char (point-min))
256       (if (re-search-forward "HTTP/[0-9.]+ 302 Moved" nil t)
257           (if (re-search-forward
258                (concat "Location: " mixi-url "\\(.+\\)") nil t)
259               (setq ret (mixi-url-retrieve (match-string 1) post-data))
260             (setq ret (mixi-url-retrieve "/home.pl" post-data)))
261         (unless (re-search-forward "HTTP/[0-9.]+ 200 OK" nil t)
262           (error (mixi-message "Cannot retrieve")))
263         (search-forward "\n\n")
264         (setq ret (decode-coding-string
265                    (buffer-substring-no-properties (point) (point-max))
266                    mixi-coding-system))
267         (kill-buffer buffer)
268         (setq ret (mixi-parse-buffer url ret post-data))))
269     ret))
270
271 (defun mixi-w3m-retrieve (url &optional post-data)
272   "Retrieve the URL and return gotten strings."
273   (let ((url (mixi-expand-url url)))
274     (with-temp-buffer
275       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
276           (error (mixi-message "Cannot retrieve"))
277         (w3m-decode-buffer url)
278         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
279           (mixi-parse-buffer url ret post-data))))))
280
281 (defun mixi-curl-retrieve (url &optional post-data)
282   "Retrieve the URL and return gotten strings."
283   (with-temp-buffer
284     (if (fboundp 'set-buffer-multibyte)
285         (set-buffer-multibyte nil))
286     (let ((orig-mode (default-file-modes))
287           (coding-system-for-read 'binary)
288           (coding-system-for-write 'binary)
289           process ret)
290       (unwind-protect
291           (progn
292             (set-default-file-modes 448)
293             (setq process
294                   (apply #'start-process "curl" (current-buffer)
295                          mixi-curl-program
296                          (append (if post-data '("-d" "@-"))
297                                  (list "-i" "-L" "-s"
298                                        "-b" mixi-curl-cookie-file
299                                        "-c" mixi-curl-cookie-file
300                                        (mixi-expand-url url)))))
301             (set-process-sentinel process #'ignore))
302         (set-default-file-modes orig-mode))
303       (when post-data
304         (process-send-string process (concat post-data "\n"))
305         (process-send-eof process))
306       (while (eq (process-status process) 'run)
307         (accept-process-output process 1))
308       (goto-char (point-min))
309       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
310         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
311       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
312         (error (mixi-message "Cannot retrieve")))
313       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
314       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
315       (mixi-parse-buffer url ret post-data))))
316
317 (defconst mixi-my-id-regexp
318   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
319
320 (defun mixi-login (&optional email password)
321   "Login to mixi."
322   (when (and (eq mixi-retrieve-function 'mixi-w3m-retrieve)
323              (not w3m-use-cookies))
324     (error (mixi-message "Require to accept cookies.  Please set "
325                          "`w3m-use-cookies' to t.")))
326   (let ((email (or email mixi-default-email
327                    (read-from-minibuffer (mixi-message "Login Email: "))))
328         (password (or password mixi-default-password
329                       (read-passwd (mixi-message "Login Password: ")))))
330     (let ((buffer (mixi-retrieve "/login.pl"
331                                  (concat "email=" email
332                                          "&password=" password
333                                          "&next_url=/home.pl"
334                                          "&sticky=on"))))
335       (unless (string-match "url=/check\\.pl\\?n=" buffer)
336         (error (mixi-message "Cannot login")))
337       (setq buffer (mixi-retrieve "/check.pl?n=home.pl"))
338       (if (string-match mixi-my-id-regexp buffer)
339           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
340         (error (mixi-message "Cannot login"))))))
341
342 (defun mixi-logout ()
343   (mixi-retrieve "/logout.pl"))
344
345 (defmacro with-mixi-retrieve (url &rest body)
346   `(let (buffer)
347      (when ,url
348        (setq buffer (mixi-retrieve ,url))
349        (when (string-match "login.pl" buffer)
350          (mixi-login)
351          (setq buffer (mixi-retrieve ,url))))
352      ,@body))
353 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
354 (put 'with-mixi-retrieve 'edebug-form-spec '(form body))
355
356 (defun mixi-get-matched-items (url regexp &optional range)
357   "Get matched items to REGEXP in URL."
358   (let ((page 1)
359         ids)
360     (catch 'end
361       (while (or (null range) (< (length ids) range))
362         (with-mixi-retrieve (format url page)
363           (let ((pos 0)
364                 found)
365             (while (and (string-match regexp buffer pos)
366                         (or (null range) (< (length ids) range)))
367               (let ((num 1)
368                     list)
369                 (while (match-string num buffer)
370                   (setq list (cons (match-string num buffer) list))
371                   (incf num))
372                 (when (not (member (reverse list) ids))
373                   (setq found t)
374                   (setq ids (cons (reverse list) ids)))
375                 (setq pos (match-end (1- num)))))
376             (when (not found)
377               (throw 'end ids))))
378         (incf page)))
379     (reverse ids)))
380
381 ;; stolen (and modified) from shimbun.el
382 (defun mixi-remove-markup (string)
383   "Remove markups from STRING."
384   (with-temp-buffer
385     (insert (or string ""))
386     (save-excursion
387       (goto-char (point-min))
388       (while (search-forward "<!--" nil t)
389         (delete-region (match-beginning 0)
390                        (or (search-forward "-->" nil t)
391                            (point-max))))
392       (goto-char (point-min))
393       (while (re-search-forward "<[^>]+>" nil t)
394         (replace-match "" t t))
395       (goto-char (point-min))
396       (while (re-search-forward "\r" nil t)
397         (replace-match "\n" t t)))
398     ;; FIXME: Decode entities.
399     (buffer-string)))
400
401 ;; stolen (and modified) from w3m.el
402 ;; FIXME: Hmm.
403 (defun mixi-url-encode-and-quote-percent-string (string)
404   (apply (function concat)
405          (mapcar
406           (lambda (char)
407             (cond
408              ((eq char ?\n)             ; newline
409               "%%0D%%0A")
410              ((string-match "[-a-zA-Z0-9_:/.]" (char-to-string char)) ; xxx?
411               (char-to-string char))    ; printable
412              ((char-equal char ?\x20)   ; space
413               "+")
414              (t
415               (format "%%%%%02x" char))))       ; escape
416           ;; Coerce a string into a list of chars.
417           (append (encode-coding-string (or string "") mixi-coding-system)
418                   nil))))
419
420 ;; Cache.
421 ;; stolen from time-date.el
422 (defun mixi-time-less-p (t1 t2)
423   "Say whether time value T1 is less than time value T2."
424   (unless (numberp (cdr t1))
425     (setq t1 (cons (car t1) (car (cdr t1)))))
426   (unless (numberp (cdr t2))
427     (setq t2 (cons (car t2) (car (cdr t2)))))
428   (or (< (car t1) (car t2))
429       (and (= (car t1) (car t2))
430            (< (cdr t1) (cdr t2)))))
431
432 (defun mixi-time-add (t1 t2)
433   "Add two time values.  One should represent a time difference."
434   (unless (numberp (cdr t1))
435     (setq t1 (cons (car t1) (car (cdr t1)))))
436   (unless (numberp (cdr t2))
437     (setq t2 (cons (car t2) (car (cdr t2)))))
438   (let ((low (+ (cdr t1) (cdr t2))))
439     (cons (+ (car t1) (car t2) (lsh low -16)) low)))
440
441 ;; stolen from time-date.el
442 (defun mixi-seconds-to-time (seconds)
443   "Convert SECONDS (a floating point number) to a time value."
444   (cons (floor seconds 65536)
445         (floor (mod seconds 65536))))
446
447 (defun mixi-cache-expired-p (object)
448   "Whether a cache of OBJECT is expired."
449   (let ((timestamp (mixi-object-timestamp object)))
450     (unless (or (null mixi-cache-expires)
451                  (null timestamp))
452       (if (numberp mixi-cache-expires)
453           (mixi-time-less-p
454            (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
455            (current-time))
456         t))))
457
458 (defun mixi-make-cache (key value table)
459   "Make a cache object and return it."
460   (let ((cache (gethash key table)))
461     (if (and cache (not (mixi-cache-expired-p cache)))
462         cache
463       (puthash key value table))))
464
465 ;; Object.
466 (defconst mixi-object-prefix "mixi-")
467
468 (defmacro mixi-object-class (object)
469   `(car-safe ,object))
470
471 (defmacro mixi-object-p (object)
472   `(eq (string-match (concat "^" mixi-object-prefix)
473                      (symbol-name (mixi-object-class ,object))) 0))
474
475 (defun mixi-object-name (object)
476   "Return the name of OBJECT."
477   (unless (mixi-object-p object)
478     (signal 'wrong-type-argument (list 'mixi-object-p object)))
479   (let ((class (mixi-object-class object)))
480     (substring (symbol-name class) (length mixi-object-prefix))))
481
482 (defun mixi-object-timestamp (object)
483   "Return the timestamp of OJBECT."
484   (unless (mixi-object-p object)
485     (signal 'wrong-type-argument (list 'mixi-object-p object)))
486   (aref (cdr object) 0))
487 (defalias 'mixi-object-realize-p 'mixi-object-timestamp)
488
489 (defun mixi-object-owner (object)
490   "Return the owner of OBJECT."
491   (unless (mixi-object-p object)
492     (signal 'wrong-type-argument (list 'mixi-object-p object)))
493   (let ((func (intern (concat mixi-object-prefix
494                               (mixi-object-name object) "-owner"))))
495     (funcall func object)))
496
497 (defun mixi-object-id (object)
498   "Return the id of OBJECT."
499   (unless (mixi-object-p object)
500     (signal 'wrong-type-argument (list 'mixi-object-p object)))
501   (let ((func (intern (concat mixi-object-prefix
502                               (mixi-object-name object) "-id"))))
503     (funcall func object)))
504
505 (defun mixi-object-time (object)
506   "Return the time of OBJECT."
507   (unless (mixi-object-p object)
508     (signal 'wrong-type-argument (list 'mixi-object-p object)))
509   (let ((func (intern (concat mixi-object-prefix
510                               (mixi-object-name object) "-time"))))
511     (funcall func object)))
512
513 (defun mixi-object-title (object)
514   "Return the title of OBJECT."
515   (unless (mixi-object-p object)
516     (signal 'wrong-type-argument (list 'mixi-object-p object)))
517   (let ((class (mixi-object-class object))
518         (func (intern (concat mixi-object-prefix
519                               (mixi-object-name object) "-title")))
520         prefix)
521     (cond ((eq class 'mixi-event)
522            (setq prefix "[¥¤¥Ù¥ó¥È]")))
523     (concat prefix (funcall func object))))
524
525 (defun mixi-object-content (object)
526   "Return the content of OBJECT."
527   (unless (mixi-object-p object)
528     (signal 'wrong-type-argument (list 'mixi-object-p object)))
529   (let ((class (mixi-object-class object)))
530     (cond ((eq class 'mixi-event)
531            (let ((limit (mixi-event-limit object)))
532              (setq limit (if limit
533                              (format-time-string "%Yǯ%m·î%dÆü" limit)
534                            "»ØÄê¤Ê¤·"))
535              (concat "<dl><dt>³«ºÅÆü»þ¡§</dt>"
536                      "<dd>" (mixi-event-date object) "</dd>"
537                      "<dt>³«ºÅ¾ì½ê¡§</dt>"
538                      "<dd>" (mixi-event-place object) "</dd>"
539                      "<dt>¾ÜºÙ¡§</dt>"
540                      "<dd>" (mixi-event-detail object) "</dd>"
541                      "<dt>Ê罸´ü¸Â¡§</dt>"
542                      "<dd>" limit "</dd>"
543                      "<dt>»²²Ã¼Ô¡§</dt>"
544                      "<dd>" (mixi-event-members object) "</dd></dl>")))
545           (t
546            (let ((func (intern (concat mixi-object-prefix
547                                        (mixi-object-name object) "-content"))))
548              (funcall func object))))))
549
550 (defun mixi-object-set-timestamp (object timestamp)
551   "Set the timestamp of OBJECT."
552   (unless (mixi-object-p object)
553     (signal 'wrong-type-argument (list 'mixi-object-p object)))
554   (aset (cdr object) 0 timestamp))
555
556 (defmacro mixi-object-touch (object)
557   `(mixi-object-set-timestamp ,object (current-time)))
558
559 (defconst mixi-object-url-regexp
560   "/\\(show\\|view\\)_\\([a-z]+\\)\\.pl")
561
562 (defun mixi-make-object-from-url (url)
563   "Return a mixi object from URL."
564   (if (string-match mixi-object-url-regexp url)
565       (let ((name (match-string 2 url)))
566         (when (string= name "bbs")
567           (setq name "topic"))
568         (let ((func (intern (concat mixi-object-prefix "make-" name
569                                     "-from-url"))))
570           (funcall func url)))
571     (when (string-match "/home\\.pl" url)
572       (mixi-make-friend-from-url url))))
573
574 ;; Friend object.
575 (defvar mixi-friend-cache (make-hash-table :test 'equal))
576 (defun mixi-make-friend (id &optional nick)
577   "Return a friend object."
578   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick nil nil nil nil
579                                                  nil nil nil nil nil nil nil))
580                    mixi-friend-cache))
581
582 (defun mixi-make-me ()
583   "Return a my object."
584   (unless mixi-me
585     (with-mixi-retrieve "/home.pl"
586       (if (string-match mixi-my-id-regexp buffer)
587           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
588         (signal 'error (list 'who-am-i)))))
589   mixi-me)
590
591 (defconst mixi-friend-url-regexp
592   "/show_friend\\.pl\\?id=\\([0-9]+\\)")
593
594 (defun mixi-make-friend-from-url (url)
595   "Return a friend object from URL."
596   (if (string-match mixi-friend-url-regexp url)
597       (let ((id (match-string 1 url)))
598         (mixi-make-friend id))
599     (when (string-match "/home\\.pl" url)
600       (mixi-make-me))))
601
602 (defmacro mixi-friend-p (friend)
603   `(eq (mixi-object-class ,friend) 'mixi-friend))
604
605 (defmacro mixi-friend-page (friend)
606   `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
607
608 (defconst mixi-friend-nick-regexp
609   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
610 (defconst mixi-friend-name-sex-regexp
611   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>\n+<td WIDTH=345>\\([^<]+\\) (\\([Ã˽÷]\\)À­)</td>")
612 (defconst mixi-friend-address-regexp
613   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
614 (defconst mixi-friend-age-regexp
615   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
616 (defconst mixi-friend-birthday-regexp
617   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
618 (defconst mixi-friend-blood-type-regexp
619   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
620 (defconst mixi-friend-birthplace-regexp
621   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
622 (defconst mixi-friend-hobby-regexp
623   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+\\)</td></tr>")
624 (defconst mixi-friend-job-regexp
625   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
626 (defconst mixi-friend-organization-regexp
627   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
628 (defconst mixi-friend-profile-regexp
629   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
630
631 (defun mixi-friend-realize (friend)
632   "Realize a FRIEND."
633   ;; FIXME: Check a expiration of cache?
634   (unless (mixi-object-realize-p friend)
635     (let (buf)
636       (with-mixi-retrieve (mixi-friend-page friend)
637         (setq buf buffer))
638       (if (string-match mixi-friend-nick-regexp buf)
639           (mixi-friend-set-nick friend (match-string 1 buf))
640         (signal 'error (list 'cannot-find-nick friend)))
641       ;; For getting my profile.
642       (unless (string-match mixi-friend-name-sex-regexp buf)
643         (with-mixi-retrieve "/show_profile.pl"
644           (setq buf buffer)))
645       (if (string-match mixi-friend-name-sex-regexp buf)
646           (progn
647             (mixi-friend-set-name friend (match-string 2 buf))
648             (mixi-friend-set-sex friend
649                                  (if (string= (match-string 3 buf) "ÃË")
650                                      'male 'female)))
651         (signal 'error (list 'cannot-find-name-or-sex friend)))
652       (when (string-match mixi-friend-address-regexp buf)
653         (mixi-friend-set-address friend (match-string 1 buf)))
654       (when (string-match mixi-friend-age-regexp buf)
655         (mixi-friend-set-age
656          friend (string-to-number (match-string 2 buf))))
657       (when (string-match mixi-friend-birthday-regexp buf)
658         (mixi-friend-set-birthday
659          friend (list (string-to-number (match-string 1 buf))
660                       (string-to-number (match-string 2 buf)))))
661       (when (string-match mixi-friend-blood-type-regexp buf)
662         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
663       (when (string-match mixi-friend-birthplace-regexp buf)
664         (mixi-friend-set-birthplace friend (match-string 1 buf)))
665       (when (string-match mixi-friend-hobby-regexp buf)
666         (mixi-friend-set-hobby
667          friend (split-string (match-string 2 buf) ", ")))
668       (when (string-match mixi-friend-job-regexp buf)
669         (mixi-friend-set-job friend (match-string 2 buf)))
670       (when (string-match mixi-friend-organization-regexp buf)
671         (mixi-friend-set-organization friend (match-string 2 buf)))
672       (when (string-match mixi-friend-profile-regexp buf)
673         (mixi-friend-set-profile friend (match-string 1 buf))))
674     (mixi-object-touch friend)))
675
676 (defun mixi-friend-id (friend)
677   "Return the id of FRIEND."
678   (unless (mixi-friend-p friend)
679     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
680   (aref (cdr friend) 1))
681
682 (defun mixi-friend-nick (friend)
683   "Return the nick of FRIEND."
684   (unless (mixi-friend-p friend)
685     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
686   (unless (aref (cdr friend) 2)
687     (mixi-friend-realize friend))
688   (aref (cdr friend) 2))
689
690 (defun mixi-friend-name (friend)
691   "Return the name of FRIEND."
692   (unless (mixi-friend-p friend)
693     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
694   (mixi-friend-realize friend)
695   (aref (cdr friend) 3))
696
697 (defun mixi-friend-sex (friend)
698   "Return the sex of FRIEND."
699   (unless (mixi-friend-p friend)
700     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
701   (mixi-friend-realize friend)
702   (aref (cdr friend) 4))
703
704 (defun mixi-friend-address (friend)
705   "Return the address of FRIEND."
706   (unless (mixi-friend-p friend)
707     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
708   (mixi-friend-realize friend)
709   (aref (cdr friend) 5))
710
711 (defun mixi-friend-age (friend)
712   "Return the age of FRIEND."
713   (unless (mixi-friend-p friend)
714     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
715   (mixi-friend-realize friend)
716   (aref (cdr friend) 6))
717
718 (defun mixi-friend-birthday (friend)
719   "Return the birthday of FRIEND."
720   (unless (mixi-friend-p friend)
721     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
722   (mixi-friend-realize friend)
723   (aref (cdr friend) 7))
724
725 (defun mixi-friend-blood-type (friend)
726   "Return the blood type of FRIEND."
727   (unless (mixi-friend-p friend)
728     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
729   (mixi-friend-realize friend)
730   (aref (cdr friend) 8))
731
732 (defun mixi-friend-birthplace (friend)
733   "Return the birthplace of FRIEND."
734   (unless (mixi-friend-p friend)
735     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
736   (mixi-friend-realize friend)
737   (aref (cdr friend) 9))
738
739 (defun mixi-friend-hobby (friend)
740   "Return the hobby of FRIEND."
741   (unless (mixi-friend-p friend)
742     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
743   (mixi-friend-realize friend)
744   (aref (cdr friend) 10))
745
746 (defun mixi-friend-job (friend)
747   "Return the job of FRIEND."
748   (unless (mixi-friend-p friend)
749     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
750   (mixi-friend-realize friend)
751   (aref (cdr friend) 11))
752
753 (defun mixi-friend-organization (friend)
754   "Return the organization of FRIEND."
755   (unless (mixi-friend-p friend)
756     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
757   (mixi-friend-realize friend)
758   (aref (cdr friend) 12))
759
760 (defun mixi-friend-profile (friend)
761   "Return the pforile of FRIEND."
762   (unless (mixi-friend-p friend)
763     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
764   (mixi-friend-realize friend)
765   (aref (cdr friend) 13))
766
767 (defun mixi-friend-set-nick (friend nick)
768   "Set the nick of FRIEND."
769   (unless (mixi-friend-p friend)
770     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
771   (aset (cdr friend) 2 nick))
772
773 (defun mixi-friend-set-name (friend name)
774   "Set the name of FRIEND."
775   (unless (mixi-friend-p friend)
776     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
777   (aset (cdr friend) 3 name))
778
779 (defun mixi-friend-set-sex (friend sex)
780   "Set the sex of FRIEND."
781   (unless (mixi-friend-p friend)
782     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
783   (aset (cdr friend) 4 sex))
784
785 (defun mixi-friend-set-address (friend address)
786   "Set the address of FRIEND."
787   (unless (mixi-friend-p friend)
788     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
789   (aset (cdr friend) 5 address))
790
791 (defun mixi-friend-set-age (friend age)
792   "Set the age of FRIEND."
793   (unless (mixi-friend-p friend)
794     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
795   (aset (cdr friend) 6 age))
796
797 (defun mixi-friend-set-birthday (friend birthday)
798   "Set the birthday of FRIEND."
799   (unless (mixi-friend-p friend)
800     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
801   (aset (cdr friend) 7 birthday))
802
803 (defun mixi-friend-set-blood-type (friend blood-type)
804   "Set the blood type of FRIEND."
805   (unless (mixi-friend-p friend)
806     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
807   (aset (cdr friend) 8 blood-type))
808
809 (defun mixi-friend-set-birthplace (friend birthplace)
810   "Set the birthplace of FRIEND."
811   (unless (mixi-friend-p friend)
812     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
813   (aset (cdr friend) 9 birthplace))
814
815 (defun mixi-friend-set-hobby (friend hobby)
816   "Set the hobby of FRIEND."
817   (unless (mixi-friend-p friend)
818     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
819   (aset (cdr friend) 10 hobby))
820
821 (defun mixi-friend-set-job (friend job)
822   "Set the job of FRIEND."
823   (unless (mixi-friend-p friend)
824     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
825   (aset (cdr friend) 11 job))
826
827 (defun mixi-friend-set-organization (friend organization)
828   "Set the organization of FRIEND."
829   (unless (mixi-friend-p friend)
830     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
831   (aset (cdr friend) 12 organization))
832
833 (defun mixi-friend-set-profile (friend profile)
834   "Set the profile of FRIEND."
835   (unless (mixi-friend-p friend)
836     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
837   (aset (cdr friend) 13 profile))
838
839 (defmacro mixi-friend-list-page (&optional friend)
840   `(concat "/list_friend.pl?page=%d"
841            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
842
843 (defconst mixi-friend-list-id-regexp
844   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
845 (defconst mixi-friend-list-nick-regexp
846   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
847
848 (defun mixi-get-friends (&rest args)
849   "Get friends of FRIEND."
850   (when (> (length args) 2)
851     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
852   (let ((friend (nth 0 args))
853         (range (nth 1 args)))
854     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
855       (setq friend (nth 1 args))
856       (setq range (nth 0 args)))
857     (unless (or (null friend) (mixi-friend-p friend))
858       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
859     (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
860                                        mixi-friend-list-id-regexp
861                                        range))
862           (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
863                                          mixi-friend-list-nick-regexp
864                                          range)))
865       (let ((index 0)
866             ret)
867         (while (< index (length ids))
868           (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
869                                             (nth 0 (nth index nicks))) ret))
870           (incf index))
871         (reverse ret)))))
872
873 ;; Favorite.
874 (defmacro mixi-favorite-list-page ()
875   `(concat "/list_bookmark.pl?page=%d"))
876
877 (defconst mixi-favorite-list-id-regexp
878   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
879 (defconst mixi-favorite-list-nick-regexp
880   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
881 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
882
883 (defun mixi-get-favorites (&optional range)
884   "Get favorites."
885   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
886                                      mixi-favorite-list-id-regexp
887                                      range))
888         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
889                                        mixi-favorite-list-nick-regexp
890                                        range)))
891     (let ((index 0)
892           ret)
893       (while (< index (length ids))
894         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
895                                           (nth 0 (nth index nicks))) ret))
896         (incf index))
897       (reverse ret))))
898
899 ;; Log object.
900 (defun mixi-make-log (friend time)
901   "Return a log object."
902   (cons 'mixi-log (vector friend time)))
903
904 (defmacro mixi-log-p (log)
905   `(eq (mixi-object-class ,log) 'mixi-log))
906
907 (defun mixi-log-friend (log)
908   "Return the friend of LOG."
909   (unless (mixi-log-p log)
910     (signal 'wrong-type-argument (list 'mixi-log-p log)))
911   (aref (cdr log) 0))
912
913 (defun mixi-log-time (log)
914   "Return the time of LOG."
915   (unless (mixi-log-p log)
916     (signal 'wrong-type-argument (list 'mixi-log-p log)))
917   (aref (cdr log) 1))
918
919 (defmacro mixi-log-list-page ()
920   `(concat "/show_log.pl"))
921
922 (defconst mixi-log-list-regexp
923   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a></li>")
924
925 (defun mixi-get-logs (&optional range)
926   "Get logs."
927   (let ((items (mixi-get-matched-items (mixi-log-list-page)
928                                        mixi-log-list-regexp
929                                        range)))
930     (mapcar (lambda (item)
931               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
932                              (encode-time 0
933                                           (string-to-number (nth 4 item))
934                                           (string-to-number (nth 3 item))
935                                           (string-to-number (nth 2 item))
936                                           (string-to-number (nth 1 item))
937                                           (string-to-number (nth 0 item)))))
938             items)))
939
940 ;; Diary object.
941 (defvar mixi-diary-cache (make-hash-table :test 'equal))
942 (defun mixi-make-diary (owner id)
943   "Return a diary object."
944   (let ((owner (or owner (mixi-make-me))))
945     (mixi-make-cache (list (mixi-friend-id owner) id)
946                      (cons 'mixi-diary (vector nil owner id nil nil nil))
947                      mixi-diary-cache)))
948
949 (defconst mixi-diary-url-regexp
950   "/view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)")
951
952 (defun mixi-make-diary-from-url (url)
953   "Return a diary object from URL."
954   (when (string-match mixi-diary-url-regexp url)
955     (let ((id (match-string 1 url))
956           (owner-id (match-string 2 url)))
957       (mixi-make-diary (mixi-make-friend owner-id) id))))
958
959 (defmacro mixi-diary-p (diary)
960   `(eq (mixi-object-class ,diary) 'mixi-diary))
961
962 (defmacro mixi-diary-page (diary)
963   `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
964            "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
965
966 (defconst mixi-diary-closed-regexp
967   "<td>ͧ¿Í\\(¤Îͧ¿Í\\)?¤Þ¤Ç¸ø³«¤Î¤¿¤áÆɤळ¤È¤¬½ÐÍè¤Þ¤»¤ó¡£</td></tr>")
968 (defconst mixi-diary-owner-nick-regexp
969   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+?\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
970 (defconst mixi-diary-time-regexp
971   "<td ALIGN=center ROWSPAN=2 NOWRAP WIDTH=95 bgcolor=#FFD8B0>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
972 (defconst mixi-diary-title-regexp
973   "<td BGCOLOR=#FFF4E0 WIDTH=430>&nbsp;\\([^<]+\\)</td></tr>")
974 (defconst mixi-diary-content-regexp
975   "<td CLASS=h12>\\(.*\\)</td></tr>")
976
977 (defun mixi-diary-realize (diary)
978   "Realize a DIARY."
979   ;; FIXME: Check a expiration of cache?
980   (unless (mixi-object-realize-p diary)
981     (with-mixi-retrieve (mixi-diary-page diary)
982       (unless (string-match mixi-diary-closed-regexp buffer)
983         (if (string-match mixi-diary-owner-nick-regexp buffer)
984             (mixi-friend-set-nick (mixi-diary-owner diary)
985                                   (match-string 1 buffer))
986           (signal 'error (list 'cannot-find-owner-nick diary)))
987         (if (string-match mixi-diary-time-regexp buffer)
988             (mixi-diary-set-time
989              diary (encode-time 0 (string-to-number (match-string 5 buffer))
990                                 (string-to-number (match-string 4 buffer))
991                                 (string-to-number (match-string 3 buffer))
992                                 (string-to-number (match-string 2 buffer))
993                                 (string-to-number (match-string 1 buffer))))
994           (signal 'error (list 'cannot-find-time diary)))
995         (if (string-match mixi-diary-title-regexp buffer)
996             (mixi-diary-set-title diary (match-string 1 buffer))
997           (signal 'error (list 'cannot-find-title diary)))
998         (if (string-match mixi-diary-content-regexp buffer)
999             (mixi-diary-set-content diary (match-string 1 buffer))
1000           (signal 'error (list 'cannot-find-content diary)))))
1001     (mixi-object-touch diary)))
1002
1003 (defun mixi-diary-owner (diary)
1004   "Return the owner of DIARY."
1005   (unless (mixi-diary-p diary)
1006     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1007   (aref (cdr diary) 1))
1008
1009 (defun mixi-diary-id (diary)
1010   "Return the id of DIARY."
1011   (unless (mixi-diary-p diary)
1012     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1013   (aref (cdr diary) 2))
1014
1015 (defun mixi-diary-time (diary)
1016   "Return the time of DIARY."
1017   (unless (mixi-diary-p diary)
1018     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1019   (mixi-diary-realize diary)
1020   (aref (cdr diary) 3))
1021
1022 (defun mixi-diary-title (diary)
1023   "Return the title of DIARY."
1024   (unless (mixi-diary-p diary)
1025     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1026   (mixi-diary-realize diary)
1027   (aref (cdr diary) 4))
1028
1029 (defun mixi-diary-content (diary)
1030   "Return the content of DIARY."
1031   (unless (mixi-diary-p diary)
1032     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1033   (mixi-diary-realize diary)
1034   (aref (cdr diary) 5))
1035
1036 (defun mixi-diary-set-time (diary time)
1037   "Set the time of DIARY."
1038   (unless (mixi-diary-p diary)
1039     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1040   (aset (cdr diary) 3 time))
1041
1042 (defun mixi-diary-set-title (diary title)
1043   "Set the title of DIARY."
1044   (unless (mixi-diary-p diary)
1045     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1046   (aset (cdr diary) 4 title))
1047
1048 (defun mixi-diary-set-content (diary content)
1049   "Set the content of DIARY."
1050   (unless (mixi-diary-p diary)
1051     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1052   (aset (cdr diary) 5 content))
1053
1054 (defmacro mixi-diary-list-page (&optional friend)
1055   `(concat "/list_diary.pl?page=%d"
1056            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1057
1058 (defconst mixi-diary-list-regexp
1059   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
1060
1061 (defun mixi-get-diaries (&rest args)
1062   "Get diaries of FRIEND."
1063   (when (> (length args) 2)
1064     (signal 'wrong-number-of-arguments
1065             (list 'mixi-get-diaries (length args))))
1066   (let ((friend (nth 0 args))
1067         (range (nth 1 args)))
1068     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1069       (setq friend (nth 1 args))
1070       (setq range (nth 0 args)))
1071     (unless (or (null friend) (mixi-friend-p friend))
1072       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1073     (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
1074                                          mixi-diary-list-regexp
1075                                          range)))
1076       (mapcar (lambda (item)
1077                 (mixi-make-diary friend (nth 0 item)))
1078               items))))
1079
1080 (defmacro mixi-new-diary-list-page ()
1081   `(concat "/new_friend_diary.pl?page=%d"))
1082
1083 (defconst mixi-new-diary-list-regexp
1084   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
1085
1086 (defun mixi-get-new-diaries (&optional range)
1087   "Get new diaries."
1088   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
1089                                        mixi-new-diary-list-regexp
1090                                        range)))
1091     (mapcar (lambda (item)
1092               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
1093             items)))
1094
1095 (defmacro mixi-search-diary-list-page (keyword)
1096   `(concat "/search_diary.pl?page=%d&submit=search&keyword="
1097              (mixi-url-encode-and-quote-percent-string keyword)))
1098
1099 (defconst mixi-search-diary-list-regexp
1100   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)\">")
1101
1102 (defun mixi-search-diaries (keyword &optional range)
1103   (let ((items (mixi-get-matched-items (mixi-search-diary-list-page keyword)
1104                                        mixi-search-diary-list-regexp
1105                                        range)))
1106     (mapcar (lambda (item)
1107               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
1108             items)))
1109
1110 ;; Community object.
1111 (defvar mixi-community-cache (make-hash-table :test 'equal))
1112 (defun mixi-make-community (id &optional name)
1113   "Return a community object."
1114   (mixi-make-cache id (cons 'mixi-community (vector nil id name nil nil nil
1115                                                     nil nil nil nil))
1116                    mixi-community-cache))
1117
1118 (defconst mixi-community-url-regexp
1119   "/view_community\\.pl\\?id=\\([0-9]+\\)")
1120
1121 (defun mixi-make-community-from-url (url)
1122   "Return a community object from URL."
1123   (when (string-match mixi-community-url-regexp url)
1124     (let ((id (match-string 1 url)))
1125       (mixi-make-community id))))
1126
1127 (defmacro mixi-community-p (community)
1128   `(eq (mixi-object-class ,community) 'mixi-community))
1129
1130 (defmacro mixi-community-page (community)
1131   `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
1132
1133 ;; FIXME: Not community specific.
1134 (defconst mixi-community-nodata-regexp
1135   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1136 (defconst mixi-community-name-regexp
1137   "<td WIDTH=345>\\(.*\\)</td></tr>")
1138 (defconst mixi-community-birthday-regexp
1139   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>³«ÀßÆü</font></td>\n<td>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1140 ;; FIXME: Care when the owner has seceded.
1141 (defconst mixi-community-owner-regexp
1142   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>´ÉÍý¿Í</font></td>\n<td>\n\n<a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\n\\(.*\\)</a>")
1143 (defconst mixi-community-category-regexp
1144   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\n<td>\\([^<]+\\)</td>")
1145 (defconst mixi-community-members-regexp
1146   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\n<td>\\([0-9]+\\)¿Í</td></tr>")
1147 (defconst mixi-community-open-level-regexp
1148   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>
1149 <td>\\(.+\\)</td></tr>")
1150 (defconst mixi-community-authority-regexp
1151   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\n<td>\\(.+\\)</td></tr>")
1152 (defconst mixi-community-description-regexp
1153   "<td CLASS=h120>\\(.+\\)</td>")
1154
1155 (defun mixi-community-realize (community)
1156   "Realize a COMMUNITY."
1157   ;; FIXME: Check a expiration of cache?
1158   (unless (mixi-object-realize-p community)
1159     (with-mixi-retrieve (mixi-community-page community)
1160       (if (string-match mixi-community-nodata-regexp buffer)
1161           ;; FIXME: Set all members?
1162           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1163         (if (string-match mixi-community-name-regexp buffer)
1164             (mixi-community-set-name community (match-string 1 buffer))
1165           (signal 'error (list 'cannot-find-name community)))
1166         (if (string-match mixi-community-birthday-regexp buffer)
1167             (mixi-community-set-birthday
1168              community
1169              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
1170                           (string-to-number (match-string 2 buffer))
1171                           (string-to-number (match-string 1 buffer))))
1172           (signal 'error (list 'cannot-find-birthday community)))
1173         (if (string-match mixi-community-owner-regexp buffer)
1174             (if (string= (match-string 1 buffer) "home.pl")
1175                 (mixi-community-set-owner community (mixi-make-me))
1176               (mixi-community-set-owner
1177                community (mixi-make-friend (match-string 2 buffer)
1178                                            (match-string 3 buffer))))
1179           (signal 'error (list 'cannot-find-owner community)))
1180         (if (string-match mixi-community-category-regexp buffer)
1181             (mixi-community-set-category community (match-string 1 buffer))
1182           (signal 'error (list 'cannot-find-category community)))
1183         (if (string-match mixi-community-members-regexp buffer)
1184             (mixi-community-set-members
1185              community (string-to-number (match-string 1 buffer)))
1186           (signal 'error (list 'cannot-find-members community)))
1187         (if (string-match mixi-community-open-level-regexp buffer)
1188             (mixi-community-set-open-level community (match-string 1 buffer))
1189           (signal 'error (list 'cannot-find-open-level community)))
1190         (if (string-match mixi-community-authority-regexp buffer)
1191             (mixi-community-set-authority community (match-string 1 buffer))
1192           (signal 'error (list 'cannot-find-authority community)))
1193         (if (string-match mixi-community-description-regexp buffer)
1194             (mixi-community-set-description community (match-string 1 buffer))
1195           (signal 'error (list 'cannot-find-description community)))))
1196     (mixi-object-touch community)))
1197
1198 (defun mixi-community-id (community)
1199   "Return the id of COMMUNITY."
1200   (unless (mixi-community-p community)
1201     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1202   (aref (cdr community) 1))
1203
1204 (defun mixi-community-name (community)
1205   "Return the name of COMMUNITY."
1206   (unless (mixi-community-p community)
1207     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1208   (unless (aref (cdr community) 2)
1209     (mixi-community-realize community))
1210   (aref (cdr community) 2))
1211
1212 (defun mixi-community-birthday (community)
1213   "Return the birthday of COMMUNITY."
1214   (unless (mixi-community-p community)
1215     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1216   (mixi-community-realize community)
1217   (aref (cdr community) 3))
1218
1219 (defun mixi-community-owner (community)
1220   "Return the owner of COMMUNITY."
1221   (unless (mixi-community-p community)
1222     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1223   (mixi-community-realize community)
1224   (aref (cdr community) 4))
1225
1226 (defun mixi-community-category (community)
1227   "Return the category of COMMUNITY."
1228   (unless (mixi-community-p community)
1229     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1230   (mixi-community-realize community)
1231   (aref (cdr community) 5))
1232
1233 (defun mixi-community-members (community)
1234   "Return the members of COMMUNITY."
1235   (unless (mixi-community-p community)
1236     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1237   (mixi-community-realize community)
1238   (aref (cdr community) 6))
1239
1240 (defun mixi-community-open-level (community)
1241   "Return the open-level of COMMUNITY."
1242   (unless (mixi-community-p community)
1243     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1244   (mixi-community-realize community)
1245   (aref (cdr community) 7))
1246
1247 (defun mixi-community-authority (community)
1248   "Return the authority of COMMUNITY."
1249   (unless (mixi-community-p community)
1250     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1251   (mixi-community-realize community)
1252   (aref (cdr community) 8))
1253
1254 (defun mixi-community-description (community)
1255   "Return the description of COMMUNITY."
1256   (unless (mixi-community-p community)
1257     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1258   (mixi-community-realize community)
1259   (aref (cdr community) 9))
1260
1261 (defun mixi-community-set-name (community name)
1262   "Set the name of COMMUNITY."
1263   (unless (mixi-community-p community)
1264     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1265   (aset (cdr community) 2 name))
1266
1267 (defun mixi-community-set-birthday (community birthday)
1268   "Set the birthday of COMMUNITY."
1269   (unless (mixi-community-p community)
1270     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1271   (aset (cdr community) 3 birthday))
1272
1273 (defun mixi-community-set-owner (community owner)
1274   "Set the owner of COMMUNITY."
1275   (unless (mixi-community-p community)
1276     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1277   (unless (mixi-friend-p owner)
1278     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1279   (aset (cdr community) 4 owner))
1280
1281 (defun mixi-community-set-category (community category)
1282   "Set the category of COMMUNITY."
1283   (unless (mixi-community-p community)
1284     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1285   (aset (cdr community) 5 category))
1286
1287 (defun mixi-community-set-members (community members)
1288   "Set the name of COMMUNITY."
1289   (unless (mixi-community-p community)
1290     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1291   (aset (cdr community) 6 members))
1292
1293 (defun mixi-community-set-open-level (community open-level)
1294   "Set the name of COMMUNITY."
1295   (unless (mixi-community-p community)
1296     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1297   (aset (cdr community) 7 open-level))
1298
1299 (defun mixi-community-set-authority (community authority)
1300   "Set the name of COMMUNITY."
1301   (unless (mixi-community-p community)
1302     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1303   (aset (cdr community) 8 authority))
1304
1305 (defun mixi-community-set-description (community description)
1306   "Set the name of COMMUNITY."
1307   (unless (mixi-community-p community)
1308     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1309   (aset (cdr community) 9 description))
1310
1311 (defmacro mixi-community-list-page (&optional friend)
1312   `(concat "/list_community.pl?page=%d"
1313            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1314
1315 (defconst mixi-community-list-id-regexp
1316   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1317 (defconst mixi-community-list-name-regexp
1318   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1319
1320 (defun mixi-get-communities (&rest args)
1321   "Get communities of FRIEND."
1322   (when (> (length args) 2)
1323     (signal 'wrong-number-of-arguments
1324             (list 'mixi-get-communities (length args))))
1325   (let ((friend (nth 0 args))
1326         (range (nth 1 args)))
1327     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1328       (setq friend (nth 1 args))
1329       (setq range (nth 0 args)))
1330     (unless (or (null friend) (mixi-friend-p friend))
1331       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1332     (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1333                                        mixi-community-list-id-regexp
1334                                        range))
1335           (names (mixi-get-matched-items (mixi-community-list-page friend)
1336                                          mixi-community-list-name-regexp
1337                                          range)))
1338       (let ((index 0)
1339             ret)
1340         (while (< index (length ids))
1341           (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1342                                                (nth 0 (nth index names))) ret))
1343           (incf index))
1344         (reverse ret)))))
1345
1346 (defmacro mixi-search-community-list-page (keyword)
1347   `(concat "/search_community.pl?page=%d&&sort=date&type=com&submit=main"
1348            "&keyword=" (mixi-url-encode-and-quote-percent-string keyword)
1349            "&category_id=0"))
1350
1351 (defconst mixi-search-community-list-regexp
1352   "<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>
1353 <td NOWRAP WIDTH=90 BGCOLOR=#FDF9F2><font COLOR=#996600>¥³¥ß¥å¥Ë¥Æ¥£Ì¾</font></td>
1354 <td COLSPAN=2 WIDTH=370 BGCOLOR=#FFFFFF>\\([^<]+\\)</td></tr>")
1355
1356 ;; FIXME: Support category.
1357 (defun mixi-search-communities (keyword &optional range)
1358   (let ((items (mixi-get-matched-items (mixi-search-community-list-page
1359                                         keyword)
1360                                        mixi-search-community-list-regexp
1361                                        range)))
1362     (mapcar (lambda (item)
1363               (mixi-make-community (nth 0 item) (nth 1 item)))
1364             items)))
1365
1366 ;; Topic object.
1367 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1368 (defun mixi-make-topic (community id)
1369   "Return a topic object."
1370   (mixi-make-cache (list (mixi-community-id community) id)
1371                    (cons 'mixi-topic (vector nil community id nil nil nil nil))
1372                    mixi-topic-cache))
1373
1374 (defconst mixi-topic-url-regexp
1375   "/view_bbs\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1376
1377 (defun mixi-make-topic-from-url (url)
1378   "Return a topic object from URL."
1379   (when (string-match mixi-topic-url-regexp url)
1380     (let ((id (match-string 1 url))
1381           (community-id (match-string 3 url)))
1382       (mixi-make-topic (mixi-make-community community-id) id))))
1383
1384 (defmacro mixi-topic-p (topic)
1385   `(eq (mixi-object-class ,topic) 'mixi-topic))
1386
1387 (defmacro mixi-topic-page (topic)
1388   `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
1389            "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
1390
1391 (defconst mixi-topic-time-regexp
1392   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1393 (defconst mixi-topic-title-regexp
1394   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1395 (defconst mixi-topic-owner-regexp
1396   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*?\\)\\(¤µ¤ó\\)?</a>")
1397 (defconst mixi-topic-content-regexp
1398   "<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]+&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>")
1399
1400 (defun mixi-topic-realize (topic)
1401   "Realize a TOPIC."
1402   ;; FIXME: Check a expiration of cache?
1403   (unless (mixi-object-realize-p topic)
1404     (with-mixi-retrieve (mixi-topic-page topic)
1405       (if (string-match mixi-topic-time-regexp buffer)
1406           (mixi-topic-set-time
1407            topic (encode-time 0 (string-to-number (match-string 5 buffer))
1408                               (string-to-number (match-string 4 buffer))
1409                               (string-to-number (match-string 3 buffer))
1410                               (string-to-number (match-string 2 buffer))
1411                               (string-to-number (match-string 1 buffer))))
1412         (signal 'error (list 'cannot-find-time topic)))
1413       (if (string-match mixi-topic-title-regexp buffer)
1414           (mixi-topic-set-title topic (match-string 1 buffer))
1415         (signal 'error (list 'cannot-find-title topic)))
1416       (if (string-match mixi-topic-owner-regexp buffer)
1417           (mixi-topic-set-owner topic
1418                                 (mixi-make-friend (match-string 1 buffer)
1419                                                   (match-string 2 buffer)))
1420         (signal 'error (list 'cannot-find-owner topic)))
1421       (if (string-match mixi-topic-content-regexp buffer)
1422           (mixi-topic-set-content topic (match-string 2 buffer))
1423         (signal 'error (list 'cannot-find-content topic))))
1424     (mixi-object-touch topic)))
1425
1426 (defun mixi-topic-community (topic)
1427   "Return the community of TOPIC."
1428   (unless (mixi-topic-p topic)
1429     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1430   (aref (cdr topic) 1))
1431
1432 (defun mixi-topic-id (topic)
1433   "Return the id of TOPIC."
1434   (unless (mixi-topic-p topic)
1435     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1436   (aref (cdr topic) 2))
1437
1438 (defun mixi-topic-time (topic)
1439   "Return the time of TOPIC."
1440   (unless (mixi-topic-p topic)
1441     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1442   (mixi-topic-realize topic)
1443   (aref (cdr topic) 3))
1444
1445 (defun mixi-topic-title (topic)
1446   "Return the title of TOPIC."
1447   (unless (mixi-topic-p topic)
1448     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1449   (mixi-topic-realize topic)
1450   (aref (cdr topic) 4))
1451
1452 (defun mixi-topic-owner (topic)
1453   "Return the owner of TOPIC."
1454   (unless (mixi-topic-p topic)
1455     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1456   (mixi-topic-realize topic)
1457   (aref (cdr topic) 5))
1458
1459 (defun mixi-topic-content (topic)
1460   "Return the content of TOPIC."
1461   (unless (mixi-topic-p topic)
1462     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1463   (mixi-topic-realize topic)
1464   (aref (cdr topic) 6))
1465
1466 (defun mixi-topic-set-time (topic time)
1467   "Set the time of TOPIC."
1468   (unless (mixi-topic-p topic)
1469     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1470   (aset (cdr topic) 3 time))
1471
1472 (defun mixi-topic-set-title (topic title)
1473   "Set the title of TOPIC."
1474   (unless (mixi-topic-p topic)
1475     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1476   (aset (cdr topic) 4 title))
1477
1478 (defun mixi-topic-set-owner (topic owner)
1479   "Set the owner of TOPIC."
1480   (unless (mixi-topic-p topic)
1481     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1482   (unless (mixi-friend-p owner)
1483     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1484   (aset (cdr topic) 5 owner))
1485
1486 (defun mixi-topic-set-content (topic content)
1487   "Set the content of TOPIC."
1488   (unless (mixi-topic-p topic)
1489     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1490   (aset (cdr topic) 6 content))
1491
1492 ;; Event object.
1493 (defvar mixi-event-cache (make-hash-table :test 'equal))
1494 (defun mixi-make-event (community id)
1495   "Return a event object."
1496   (mixi-make-cache (list (mixi-community-id community) id)
1497                    (cons 'mixi-event (vector nil community id nil nil nil nil
1498                                              nil nil nil nil))
1499                    mixi-event-cache))
1500
1501 (defconst mixi-event-url-regexp
1502   "/view_event\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1503
1504 (defun mixi-make-event-from-url (url)
1505   "Return a event object from URL."
1506   (when (string-match mixi-event-url-regexp url)
1507     (let ((id (match-string 1 url))
1508           (community-id (match-string 3 url)))
1509       (mixi-make-event (mixi-make-community community-id) id))))
1510
1511 (defmacro mixi-event-p (event)
1512   `(eq (mixi-object-class ,event) 'mixi-event))
1513
1514 (defmacro mixi-event-page (event)
1515   `(concat "/view_event.pl?id=" (mixi-event-id ,event)
1516            "&comm_id=" (mixi-community-id (mixi-event-community ,event))))
1517
1518 (defconst mixi-event-time-regexp
1519   "<td ROWSPAN=11 BGCOLOR=#FFD8B0 ALIGN=center VALIGN=top WIDTH=110>
1520 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1521 \\([0-9]+\\):\\([0-9]+\\)</td>")
1522 (defconst mixi-event-title-regexp
1523   "<td bgcolor=#FFF4E0 width=410>&nbsp;\\([^<]+\\)</td>")
1524 (defconst mixi-event-owner-regexp
1525   "<td BGCOLOR=#FDF9F2>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>")
1526 (defconst mixi-event-date-regexp
1527   "<td BGCOLOR=#FFFFFF ALIGN=center NOWRAP>³«ºÅÆü»þ</td>
1528 <td BGCOLOR=#FFFFFF>
1529 &nbsp;\\(.+\\)
1530 </td>")
1531 (defconst mixi-event-place-regexp
1532   "<td BGCOLOR=#FFFFFF ALIGN=center NOWRAP>³«ºÅ¾ì½ê</td>
1533 <td BGCOLOR=#FFFFFF>
1534 &nbsp;\\(.+\\)
1535 </td>")
1536 (defconst mixi-event-detail-regexp
1537   "<td BGCOLOR=#FFFFFF ALIGN=center NOWRAP>¾ÜºÙ</td>
1538 <td BGCOLOR=#FFFFFF><table BORDER=0 CELLSPACING=0 CELLPADDING=5><tr><td CLASS=h120>\\(.+\\)</td></tr></table></td>")
1539 (defconst mixi-event-limit-regexp
1540   "<td BGCOLOR=#FFFFFF ALIGN=center NOWRAP>Ê罸´ü¸Â</td>
1541 <td BGCOLOR=#FFFFFF>&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1542 (defconst mixi-event-members-regexp
1543   "<td BGCOLOR=#FFFFFF ALIGN=center NOWRAP>»²²Ã¼Ô</td>
1544 <td BGCOLOR=#FFFFFF>
1545 <table BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=100%>
1546 <tr>
1547 <td>&nbsp;\\(.+\\)</td>")
1548
1549 (defun mixi-event-realize (event)
1550   "Realize a EVENT."
1551   ;; FIXME: Check a expiration of cache?
1552   (unless (mixi-object-realize-p event)
1553     (with-mixi-retrieve (mixi-event-page event)
1554       (if (string-match mixi-event-time-regexp buffer)
1555           (mixi-event-set-time
1556            event (encode-time 0 (string-to-number (match-string 5 buffer))
1557                               (string-to-number (match-string 4 buffer))
1558                               (string-to-number (match-string 3 buffer))
1559                               (string-to-number (match-string 2 buffer))
1560                               (string-to-number (match-string 1 buffer))))
1561         (signal 'error (list 'cannot-find-time event)))
1562       (if (string-match mixi-event-title-regexp buffer)
1563           (mixi-event-set-title event (match-string 1 buffer))
1564         (signal 'error (list 'cannot-find-title event)))
1565       (if (string-match mixi-event-owner-regexp buffer)
1566           (mixi-event-set-owner event
1567                                 (mixi-make-friend (match-string 1 buffer)
1568                                                   (match-string 2 buffer)))
1569         (signal 'error (list 'cannot-find-owner event)))
1570       (if (string-match mixi-event-date-regexp buffer)
1571           (mixi-event-set-date event (match-string 1 buffer))
1572         (signal 'error (list 'cannot-find-date event)))
1573       (if (string-match mixi-event-place-regexp buffer)
1574           (mixi-event-set-place event (match-string 1 buffer))
1575         (signal 'error (list 'cannot-find-place event)))
1576       (if (string-match mixi-event-detail-regexp buffer)
1577           (mixi-event-set-detail event (match-string 1 buffer))
1578         (signal 'error (list 'cannot-find-detail event)))
1579       (when (string-match mixi-event-limit-regexp buffer)
1580         (mixi-event-set-limit
1581          event (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
1582                             (string-to-number (match-string 2 buffer))
1583                             (string-to-number (match-string 1 buffer)))))
1584       (if (string-match mixi-event-members-regexp buffer)
1585           (mixi-event-set-members event (match-string 1 buffer))
1586         (signal 'error (list 'cannot-find-members event))))
1587     (mixi-object-touch event)))
1588
1589 (defun mixi-event-community (event)
1590   "Return the community of EVENT."
1591   (unless (mixi-event-p event)
1592     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1593   (aref (cdr event) 1))
1594
1595 (defun mixi-event-id (event)
1596   "Return the id of EVENT."
1597   (unless (mixi-event-p event)
1598     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1599   (aref (cdr event) 2))
1600
1601 (defun mixi-event-time (event)
1602   "Return the time of EVENT."
1603   (unless (mixi-event-p event)
1604     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1605   (mixi-event-realize event)
1606   (aref (cdr event) 3))
1607
1608 (defun mixi-event-title (event)
1609   "Return the title of EVENT."
1610   (unless (mixi-event-p event)
1611     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1612   (mixi-event-realize event)
1613   (aref (cdr event) 4))
1614
1615 (defun mixi-event-owner (event)
1616   "Return the owner of EVENT."
1617   (unless (mixi-event-p event)
1618     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1619   (mixi-event-realize event)
1620   (aref (cdr event) 5))
1621
1622 (defun mixi-event-date (event)
1623   "Return the date of EVENT."
1624   (unless (mixi-event-p event)
1625     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1626   (mixi-event-realize event)
1627   (aref (cdr event) 6))
1628
1629 (defun mixi-event-place (event)
1630   "Return the place of EVENT."
1631   (unless (mixi-event-p event)
1632     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1633   (mixi-event-realize event)
1634   (aref (cdr event) 7))
1635
1636 (defun mixi-event-detail (event)
1637   "Return the detail of EVENT."
1638   (unless (mixi-event-p event)
1639     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1640   (mixi-event-realize event)
1641   (aref (cdr event) 8))
1642
1643 (defun mixi-event-limit (event)
1644   "Return the limit of EVENT."
1645   (unless (mixi-event-p event)
1646     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1647   (mixi-event-realize event)
1648   (aref (cdr event) 9))
1649
1650 (defun mixi-event-members (event)
1651   "Return the members of EVENT."
1652   (unless (mixi-event-p event)
1653     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1654   (mixi-event-realize event)
1655   (aref (cdr event) 10))
1656
1657 (defun mixi-event-set-time (event time)
1658   "Set the time of EVENT."
1659   (unless (mixi-event-p event)
1660     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1661   (aset (cdr event) 3 time))
1662
1663 (defun mixi-event-set-title (event title)
1664   "Set the title of EVENT."
1665   (unless (mixi-event-p event)
1666     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1667   (aset (cdr event) 4 title))
1668
1669 (defun mixi-event-set-owner (event owner)
1670   "Set the owner of EVENT."
1671   (unless (mixi-event-p event)
1672     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1673   (unless (mixi-friend-p owner)
1674     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1675   (aset (cdr event) 5 owner))
1676
1677 (defun mixi-event-set-date (event date)
1678   "Set the date of EVENT."
1679   (unless (mixi-event-p event)
1680     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1681   (aset (cdr event) 6 date))
1682
1683 (defun mixi-event-set-place (event place)
1684   "Set the place of EVENT."
1685   (unless (mixi-event-p event)
1686     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1687   (aset (cdr event) 7 place))
1688
1689 (defun mixi-event-set-detail (event detail)
1690   "Set the detail of EVENT."
1691   (unless (mixi-event-p event)
1692     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1693   (aset (cdr event) 8 detail))
1694
1695 (defun mixi-event-set-limit (event limit)
1696   "Set the limit of EVENT."
1697   (unless (mixi-event-p event)
1698     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1699   (aset (cdr event) 9 limit))
1700
1701 (defun mixi-event-set-members (event members)
1702   "Set the members of EVENT."
1703   (unless (mixi-event-p event)
1704     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1705   (aset (cdr event) 10 members))
1706
1707 ;; Bbs object.
1708 (defalias 'mixi-bbs-owner 'mixi-object-owner)
1709 (defalias 'mixi-bbs-id 'mixi-object-id)
1710 (defalias 'mixi-bbs-time 'mixi-object-time)
1711 (defalias 'mixi-bbs-title 'mixi-object-title)
1712 (defalias 'mixi-bbs-content 'mixi-object-content)
1713
1714 (defmacro mixi-bbs-list-page (community)
1715   `(concat "/list_bbs.pl?page=%d"
1716            "&id=" (mixi-community-id ,community)))
1717
1718 (defconst mixi-bbs-list-regexp
1719   "<a href=view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)")
1720
1721 (defun mixi-get-bbses (community &optional range)
1722   "Get bbese of COMMUNITY."
1723   (unless (mixi-community-p community)
1724     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1725   (let ((items (mixi-get-matched-items (mixi-bbs-list-page community)
1726                                        mixi-bbs-list-regexp
1727                                        range)))
1728     (mapcar (lambda (item)
1729               (let ((name (nth 0 item)))
1730                 (when (string= name "bbs")
1731                   (setq name "topic"))
1732                 (let ((func (intern (concat "mixi-make-" name))))
1733                   (funcall func community (nth 1 item)))))
1734             items)))
1735
1736 (defmacro mixi-new-bbs-list-page ()
1737   `(concat "/new_bbs.pl?page=%d"))
1738
1739 (defconst mixi-new-bbs-list-regexp
1740   "<a href=\"view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
1741
1742 (defun mixi-get-new-bbses (&optional range)
1743   "Get new topics."
1744   (let ((items (mixi-get-matched-items (mixi-new-bbs-list-page)
1745                                        mixi-new-bbs-list-regexp
1746                                        range)))
1747     (mapcar (lambda (item)
1748               (let ((name (nth 0 item)))
1749                 (when (string= name "bbs")
1750                   (setq name "topic"))
1751                 (let ((func (intern (concat "mixi-make-" name))))
1752                   (funcall func (mixi-make-community (nth 2 item))
1753                            (nth 1 item)))))
1754             items)))
1755
1756 ;; Comment object.
1757 (defun mixi-make-comment (parent owner time content)
1758   "Return a comment object."
1759   (cons 'mixi-comment (vector parent owner time content)))
1760
1761 (defmacro mixi-comment-p (comment)
1762   `(eq (mixi-object-class ,comment) 'mixi-comment))
1763
1764 (defun mixi-comment-parent (comment)
1765   "Return the parent of COMMENT."
1766   (unless (mixi-comment-p comment)
1767     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1768   (aref (cdr comment) 0))
1769
1770 (defun mixi-comment-owner (comment)
1771   "Return the owner of COMMENT."
1772   (unless (mixi-comment-p comment)
1773     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1774   (aref (cdr comment) 1))
1775
1776 (defun mixi-comment-time (comment)
1777   "Return the time of COMMENT."
1778   (unless (mixi-comment-p comment)
1779     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1780   (aref (cdr comment) 2))
1781
1782 (defun mixi-comment-content (comment)
1783   "Return the content of COMMENT."
1784   (unless (mixi-comment-p comment)
1785     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1786   (aref (cdr comment) 3))
1787
1788 (defun mixi-diary-comment-list-page (diary)
1789   (concat "/view_diary.pl?full=1"
1790           "&id=" (mixi-diary-id diary)
1791           "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
1792
1793 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1794 (defconst mixi-diary-comment-list-regexp
1795 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
1796 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
1797 <input type=checkbox name=comment_id value=\".+\">
1798 \\|\\)
1799 </td>
1800 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
1801 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
1802 <tr>
1803 \\(<td>\\)
1804 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
1805
1806 \\(<font color=\"#f2ddb7\">|</font> <a href=[^>]+>ºï½ü</a>
1807
1808 \\|\\)</td>
1809 </tr>
1810 </table>
1811 </td>
1812 </tr>
1813 <!-- ËÜʸ : start -->
1814 <tr>
1815 <td bgcolor=\"#ffffff\">
1816 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
1817 <tr>
1818 <td CLASS=h12>
1819 \\(.+\\)
1820 </td></tr></table>")
1821
1822 (defun mixi-topic-comment-list-page (topic)
1823   (concat "/view_bbs.pl?page=all"
1824           "&id=" (mixi-topic-id topic)
1825           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
1826
1827 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1828 (defconst mixi-topic-comment-list-regexp
1829   "<tr valign=\"top\">
1830 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
1831 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1832 \\([0-9]+\\):\\([0-9]+\\)<br>
1833 \\(<input type=\"checkbox\" name=\"comment_id\" value=\".+\">
1834 \\|\\)</td>
1835 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
1836 <b>[^<]+</b>:</font>&nbsp;
1837 \\(
1838 \\|\\) *<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
1839
1840 ?\\(
1841
1842 \\|<font color=\"#f2ddb7\">|&nbsp;</font><a href=\"delete_bbs_comment\\.pl\\?id=[0-9]+&comm_id=[0-9]+&comment_id=[0-9]+\">ºï½ü</a>
1843 \\|\\)</td>
1844 </tr>
1845 <tr>
1846 <td bgcolor=\"#ffffff\" align=\"center\">
1847 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
1848 <tr>
1849 <td class=\"h120\">
1850
1851 \\(.+\\)
1852 </td>
1853 </tr>
1854 </table>
1855 </td>
1856 </tr>")
1857
1858 (defun mixi-event-comment-list-page (event)
1859   (concat "/view_event.pl?page=all"
1860           "&id=" (mixi-event-id event)
1861           "&comm_id=" (mixi-community-id (mixi-event-community event))))
1862
1863 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1864 (defconst mixi-event-comment-list-regexp
1865   "<tr>
1866 <td ROWSPAN=2 ALIGN=center BGCOLOR=#F2DDB7 WIDTH=110>
1867 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1868 \\([0-9]+\\):\\([0-9]+\\)<br>
1869 \\(</td>\\)
1870 \\(<td BGCOLOR=#FDF9F2>\\)
1871 <font COLOR=#F8A448><b>[^<]+</b> :</font>
1872 <a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
1873
1874 </td>
1875 </tr>
1876 \\(<tr>\\)
1877 <td ALIGN=center BGCOLOR=#FFFFFF>
1878 <table BORDER=0 CELLSPACING=0 CELLPADDING=5 WIDTH=500>
1879 <tr><td CLASS=h120>\\(.+\\)</td></tr>
1880 </table>
1881 </td>
1882 </tr>")
1883
1884 (defun mixi-get-comments (parent &optional range)
1885   "Get comments of PARENT."
1886   (unless (mixi-object-p parent)
1887     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
1888   (let* ((name (mixi-object-name parent))
1889          (list-page (intern (concat mixi-object-prefix name
1890                                     "-comment-list-page")))
1891          (regexp (eval (intern (concat mixi-object-prefix name
1892                                        "-comment-list-regexp")))))
1893     (let ((items (mixi-get-matched-items
1894                   (funcall list-page parent) regexp)))
1895       (let (list)
1896         (catch 'stop
1897           (mapc (lambda (item)
1898                   (when (and (numberp range)
1899                              (>= (length list) range))
1900                     (throw 'stop nil))
1901                   (setq list (cons item list)))
1902                 (reverse items)))
1903         (setq items (reverse list)))
1904       (mapcar (lambda (item)
1905                 (mixi-make-comment parent (mixi-make-friend
1906                                            (nth 7 item) (nth 8 item))
1907                                    (encode-time
1908                                     0
1909                                     (string-to-number (nth 4 item))
1910                                     (string-to-number (nth 3 item))
1911                                     (string-to-number (nth 2 item))
1912                                     (string-to-number (nth 1 item))
1913                                     (string-to-number (nth 0 item)))
1914                                    (nth 10 item)))
1915               items))))
1916
1917 (defmacro mixi-new-comment-list-page ()
1918   `(concat "/new_comment.pl?page=%d"))
1919
1920 (defconst mixi-new-comment-list-regexp
1921   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
1922
1923 (defun mixi-get-new-comments (&optional range)
1924   "Get new comments."
1925   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
1926                                        mixi-new-comment-list-regexp
1927                                        range)))
1928     (mapcar (lambda (item)
1929               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
1930             items)))
1931
1932 ;; Message object.
1933 (defconst mixi-message-box-list '(inbox outbox savebox thrash)) ; thrash?
1934
1935 (defmacro mixi-message-box-p (box)
1936   `(when (memq ,box mixi-message-box-list)
1937      t))
1938
1939 (defun mixi-message-box-name (box)
1940   "Return the name of BOX."
1941   (unless (mixi-message-box-p box)
1942     (signal 'wrong-type-argument (list 'mixi-message-box-p box)))
1943   (symbol-name box))
1944
1945 (defvar mixi-message-cache (make-hash-table :test 'equal))
1946 (defun mixi-make-message (id box)
1947   "Return a message object."
1948   (mixi-make-cache (list id box)
1949                    (cons 'mixi-message (vector nil id box nil nil nil nil))
1950                    mixi-message-cache))
1951
1952 (defconst mixi-message-url-regexp
1953   "/view_message\\.pl\\?id=\\([a-z0-9]+\\)&box=\\([a-z]+\\)")
1954
1955 (defun mixi-make-message-from-url (url)
1956   "Return a message object from URL."
1957   (when (string-match mixi-message-url-regexp url)
1958     (let ((id (match-string 1 url))
1959           (box (match-string 2 url)))
1960       (mixi-make-message id box))))
1961
1962 (defmacro mixi-message-p (message)
1963   `(eq (mixi-object-class ,message) 'mixi-message))
1964
1965 (defmacro mixi-message-page (message)
1966   `(concat "/view_message.pl?id=" (mixi-message-id ,message)
1967            "&box=" (mixi-message-box ,message)))
1968
1969 (defconst mixi-message-owner-regexp
1970   "<font COLOR=#996600>\\(º¹½Ð¿Í\\|°¸&nbsp;Àè\\)</font>&nbsp;:&nbsp;<a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)\\(</a>\\|</td>\\)")
1971 (defconst mixi-message-title-regexp
1972 "<font COLOR=#996600>·ï\\(¡¡\\|&nbsp;\\)̾</font>&nbsp;:&nbsp;\\(.+\\)\n?</td>")
1973 (defconst mixi-message-time-regexp
1974 "<font COLOR=#996600>Æü\\(¡¡\\|&nbsp;\\)ÉÕ</font>&nbsp;:&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\)»þ\\([0-9]+\\)ʬ&nbsp;&nbsp;")
1975 (defconst mixi-message-content-regexp
1976   "<tr><td CLASS=h120>\\(.+\\)</td></tr>")
1977
1978 (defun mixi-message-realize (message)
1979   "Realize a MESSAGE."
1980   (unless (mixi-object-realize-p message)
1981     (with-mixi-retrieve (mixi-message-page message)
1982       (if (string-match mixi-message-owner-regexp buffer)
1983           (mixi-message-set-owner message
1984                                   (mixi-make-friend (match-string 2 buffer)
1985                                                     (match-string 3 buffer)))
1986         (signal 'error (list 'cannot-find-owner message)))
1987       (if (string-match mixi-message-title-regexp buffer)
1988           (mixi-message-set-title message (match-string 2 buffer))
1989         (signal 'error (list 'cannot-find-title message)))
1990       (if (string-match mixi-message-time-regexp buffer)
1991           (mixi-message-set-time
1992            message (encode-time 0 (string-to-number (match-string 6 buffer))
1993                                 (string-to-number (match-string 5 buffer))
1994                                 (string-to-number (match-string 4 buffer))
1995                                 (string-to-number (match-string 3 buffer))
1996                                 (string-to-number (match-string 2 buffer))))
1997         (signal 'error (list 'cannot-find-time message)))
1998       (if (string-match mixi-message-content-regexp buffer)
1999           (mixi-message-set-content message (match-string 1 buffer))
2000         (signal 'error (list 'cannot-find-content message))))
2001     (mixi-object-touch message)))
2002
2003 (defun mixi-message-id (message)
2004   "Return the id of MESSAGE."
2005   (unless (mixi-message-p message)
2006     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2007   (aref (cdr message) 1))
2008
2009 (defun mixi-message-box (message)
2010   "Return the box of MESSAGE."
2011   (unless (mixi-message-p message)
2012     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2013   (aref (cdr message) 2))
2014
2015 (defun mixi-message-owner (message)
2016   "Return the owner of MESSAGE."
2017   (unless (mixi-message-p message)
2018     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2019   (mixi-message-realize message)
2020   (aref (cdr message) 3))
2021
2022 (defun mixi-message-title (message)
2023   "Return the title of MESSAGE."
2024   (unless (mixi-message-p message)
2025     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2026   (mixi-message-realize message)
2027   (aref (cdr message) 4))
2028
2029 (defun mixi-message-time (message)
2030   "Return the date of MESSAGE."
2031   (unless (mixi-message-p message)
2032     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2033   (mixi-message-realize message)
2034   (aref (cdr message) 5))
2035
2036 (defun mixi-message-content (message)
2037   "Return the content of MESSAGE."
2038   (unless (mixi-message-p message)
2039     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2040   (mixi-message-realize message)
2041   (aref (cdr message) 6))
2042
2043 (defun mixi-message-set-owner (message owner)
2044   "Set the owner of MESSAGE."
2045   (unless (mixi-message-p message)
2046     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2047   (aset (cdr message) 3 owner))
2048
2049 (defun mixi-message-set-title (message title)
2050   "Set the title of MESSAGE."
2051   (unless (mixi-message-p message)
2052     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2053   (aset (cdr message) 4 title))
2054
2055 (defun mixi-message-set-time (message time)
2056   "Set the date of MESSAGE."
2057   (unless (mixi-message-p message)
2058     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2059   (aset (cdr message) 5 time))
2060
2061 (defun mixi-message-set-content (message content)
2062   "Set the content of MESSAGE."
2063   (unless (mixi-message-p message)
2064     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2065   (aset (cdr message) 6 content))
2066
2067 (defmacro mixi-message-list-page (&optional box)
2068   `(concat "/list_message.pl?page=%d"
2069            (when ,box (concat "&box=" ,box))))
2070
2071 (defconst mixi-message-list-regexp
2072   "<td><a HREF=\"view_message\\.pl\\?id=\\(.+\\)&box=\\(.+\\)\">")
2073
2074 (defun mixi-get-messages (&rest args)
2075   "Get messages."
2076   (when (> (length args) 2)
2077     (signal 'wrong-number-of-arguments
2078             (list 'mixi-get-messages (length args))))
2079   (let ((box (nth 0 args))
2080         (range (nth 1 args)))
2081     (when (or (not (mixi-message-box-p box))
2082               (mixi-message-box-p range))
2083       (setq box (nth 1 args))
2084       (setq range (nth 0 args)))
2085     (let ((items (mixi-get-matched-items
2086                   (mixi-message-list-page
2087                    (when box (mixi-message-box-name box)))
2088                   mixi-message-list-regexp
2089                   range)))
2090       (mapcar (lambda (item)
2091                 (mixi-make-message (nth 0 item) (nth 1 item)))
2092               items))))
2093
2094 ;; Introduction object.
2095 (defun mixi-make-introduction (parent owner content)
2096   "Return a introduction object."
2097   (cons 'mixi-introduction (vector parent owner content)))
2098
2099 (defmacro mixi-introduction-p (introduction)
2100   `(eq (mixi-object-class ,introduction) 'mixi-introduction))
2101
2102 (defun mixi-introduction-parent (introduction)
2103   "Return the parent of INTRODUCTION."
2104   (unless (mixi-introduction-p introduction)
2105     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2106   (aref (cdr introduction) 0))
2107
2108 (defun mixi-introduction-owner (introduction)
2109   "Return the owner of INTRODUCTION."
2110   (unless (mixi-introduction-p introduction)
2111     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2112   (aref (cdr introduction) 1))
2113
2114 (defun mixi-introduction-content (introduction)
2115   "Return the content of INTRODUCTION."
2116   (unless (mixi-introduction-p introduction)
2117     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2118   (aref (cdr introduction) 3))
2119
2120 (defmacro mixi-introduction-list-page (&optional friend)
2121   `(concat "/show_intro.pl?page=%d"
2122            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
2123
2124 (defconst mixi-introduction-list-regexp
2125   "<tr bgcolor=#FFFFFF>
2126 <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>
2127 \\(.*\\)</td></a>
2128
2129 <td WIDTH=480>
2130 \\(´Ø·¸¡§.+<br>
2131
2132
2133 \\(\\(.\\|\n<br>\\)+\\)\\|
2134 \\(\\(.\\|\n<br>\\)+\\)\\)
2135
2136
2137
2138
2139 </td>
2140 </tr>")
2141 (defconst mixi-my-introduction-list-regexp
2142   "<tr bgcolor=#FFFFFF>
2143 <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>
2144 \\(.*\\)</td></a>
2145
2146
2147 <td WIDTH=480>
2148 \\(´Ø·¸¡§.+<br>
2149
2150
2151 \\(\\(.\\|\n<br>\\)+\\)\\|
2152 \\(\\(.\\|\n<br>\\)+\\)\\)
2153
2154
2155 <br>
2156 <a href=\"edit_intro\\.pl\\?id=\\1&type=edit\">¤³¤Îͧ¿Í¤ò¾Ò²ð¤¹¤ë</a>
2157
2158
2159 <BR>
2160 <a href=\"delete_intro\\.pl\\?id=\\1\">ºï½ü</a>
2161
2162 </td>
2163 </tr>")
2164
2165 (defun mixi-get-introductions (&rest args)
2166   "Get introductions."
2167   (when (> (length args) 2)
2168     (signal 'wrong-number-of-arguments
2169             (list 'mixi-get-introduction (length args))))
2170   (let ((friend (nth 0 args))
2171         (range (nth 1 args)))
2172     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
2173       (setq friend (nth 1 args))
2174       (setq range (nth 0 args)))
2175     (unless (or (null friend) (mixi-friend-p friend))
2176       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
2177     (let* ((regexp (if friend mixi-introduction-list-regexp
2178                      mixi-my-introduction-list-regexp))
2179            (items (mixi-get-matched-items (mixi-introduction-list-page friend)
2180                                           regexp
2181                                           range)))
2182       (mapcar (lambda (item)
2183                 (mixi-make-introduction (or friend (mixi-make-me))
2184                                         (mixi-make-friend (nth 0 item)
2185                                                           (nth 1 item))
2186                                         (nth 2 item)))
2187               items))))
2188
2189 (provide 'mixi)
2190
2191 ;;; mixi.el ends here