* mixi.el (mixi-get-matched-items): Fix when MAX-NUMBERS is nil.
[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-get-communities
35 ;;  * mixi-get-topics
36 ;;  * mixi-get-new-topics
37 ;;  * mixi-get-comments
38 ;;  * mixi-get-new-comments
39
40 ;; Example:
41 ;;
42 ;; Display newest 3 diaries like a mail format.
43 ;;
44 ;; (let ((max-numbers 3)
45 ;;       (buffer (generate-new-buffer "*temp*"))
46 ;;       (format "%Y/%m/%d %H:%M"))
47 ;;   (pop-to-buffer buffer)
48 ;;   (mapc (lambda (diary)
49 ;;        (let ((subject (mixi-diary-title diary))
50 ;;              ;; Don't get owner's nick at first for omitting a useless
51 ;;              ;; retrieval.
52 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
53 ;;              (date (format-time-string format (mixi-diary-time diary)))
54 ;;              (body (mixi-diary-content diary)))
55 ;;          (insert "From: " from "\n"
56 ;;                  "Subject: " subject "\n"
57 ;;                  "Date: " date "\n\n"
58 ;;                  body "\n\n")))
59 ;;      (mixi-get-new-diaries max-numbers))
60 ;;   (set-buffer-modified-p nil)
61 ;;   (setq buffer-read-only t)
62 ;;   (goto-char (point-min)))
63 ;;
64 ;; Display newest 3 diaries including newest 3 comments like a mail format.
65 ;; Comments are displayed like a reply mail.
66 ;;
67 ;; (let ((max-numbers 3)
68 ;;       (buffer (generate-new-buffer "*temp*"))
69 ;;       (format "%Y/%m/%d %H:%M"))
70 ;;   (pop-to-buffer buffer)
71 ;;   (mapc (lambda (diary)
72 ;;        (let ((subject (mixi-diary-title diary))
73 ;;              ;; Don't get owner's nick at first for omitting a useless
74 ;;              ;; retrieval.
75 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
76 ;;              (date (format-time-string format (mixi-diary-time diary)))
77 ;;              (body (mixi-diary-content diary)))
78 ;;          (insert "From: " from "\n"
79 ;;                  "Subject: " subject "\n"
80 ;;                  "Date: " date "\n\n"
81 ;;                  body "\n\n")
82 ;;          (mapc (lambda (comment)
83 ;;                  (let ((from (mixi-friend-nick
84 ;;                               (mixi-comment-owner comment)))
85 ;;                        (subject (concat "Re: " subject))
86 ;;                        (date (format-time-string
87 ;;                               format (mixi-comment-time comment)))
88 ;;                        (body (mixi-comment-content comment)))
89 ;;                    (insert "From: " from "\n"
90 ;;                            "Subject: " subject "\n"
91 ;;                            "Date: " date "\n\n"
92 ;;                            body "\n\n")))
93 ;;                (mixi-get-comments diary max-numbers))))
94 ;;      (mixi-get-new-diaries max-numbers))
95 ;;   (set-buffer-modified-p nil)
96 ;;   (setq buffer-read-only t)
97 ;;   (goto-char (point-min)))
98
99 ;;; Code:
100
101 (condition-case nil
102     (require 'url)
103   (error))
104
105 (condition-case nil
106     (require 'w3m)
107   (error))
108
109 (eval-when-compile (require 'cl))
110
111 (defgroup mixi nil
112   "API library for accessing to mixi."
113   :group 'hypermedia)
114
115 (defcustom mixi-url "http://mixi.jp"
116   "*The URL of mixi."
117   :type 'string
118   :group 'mixi)
119
120 (defcustom mixi-coding-system 'euc-jp
121   "*Coding system for mixi."
122   :type 'coding-system
123   :group 'mixi)
124
125 (defcustom mixi-retrieve-function
126   (if (fboundp 'url-retrieve-synchronously)
127       'mixi-w3-retrieve 'mixi-w3m-retrieve)
128   "*The function for retrieving."
129   :type '(choice (const :tag "Using w3" mixi-w3-retrieve)
130                  (const :tag "Using w3m" mixi-w3m-retrieve)
131                  (const :tag "Using curl" mixi-curl-retrieve)
132                  (function :format "Other function: %v\n" :size 0))
133   :group 'mixi)
134
135 (defcustom mixi-curl-program "curl"
136   "*The program name of `curl'."
137   :type 'file
138   :group 'mixi)
139
140 (defcustom mixi-curl-cookie-file (expand-file-name "~/.mixi-cookies.txt")
141   "*The location of cookie file created by `curl'."
142   :type 'file
143   :group 'mixi)
144
145 (defcustom mixi-default-email nil
146   "*Default E-mail address that is used to login automatically."
147   :type '(choice (string :tag "E-mail address")
148                  (const :tag "Asked when it is necessary" nil))
149   :group 'mixi)
150
151 (defcustom mixi-default-password nil
152   "*Default password that is used to login automatically."
153   :type '(choice (string :tag "Password")
154                  (const :tag "Asked when it is necessary" nil))
155   :group 'mixi)
156
157 (defcustom mixi-accept-adult-contents t
158   "*If non-nil, accept to access to the adult contents."
159   :type 'boolean
160   :group 'mixi)
161
162 (defcustom mixi-continuously-access-interval 4.0
163   "*Time interval between each mixi access.
164 Increase this value when unexpected error frequently occurs."
165   :type 'number
166   :group 'mixi)
167
168 (defcustom mixi-cache-expires 3600
169   "*Seconds for expiration of a cached object."
170   :type '(choice (integer :tag "Expired seconds")
171                  (const :tag "Don't expire" nil))
172   :group 'mixi)
173
174 ;; FIXME: Not implemented.
175 (defcustom mixi-cache-use-file t
176   "*If non-nil, caches are saved to files."
177   :type 'boolean
178   :group 'mixi)
179
180 (defcustom mixi-cache-directory (expand-file-name "~/.mixi")
181   "*Where to look for cache files."
182   :type 'directory
183   :group 'mixi)
184
185 (defvar mixi-me nil)
186
187 ;; Utilities.
188 (defmacro mixi-message (string)
189   `(concat "[mixi] " ,string))
190
191 (defconst mixi-message-adult-contents
192   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
193 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
194 (defconst mixi-message-continuously-accessing
195   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
196 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
197 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
198 (defconst mixi-warning-continuously-accessing
199   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
200 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
201 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
202
203 (defun mixi-retrieve-1 (buffer url &optional post-data)
204   (when (string-match mixi-message-adult-contents buffer)
205     (if mixi-accept-adult-contents
206         (setq buffer (funcall mixi-retrieve-function url "submit=agree"))
207       (setq buffer (funcall mixi-retrieve-function (concat url "?")))))
208   (when (string-match mixi-warning-continuously-accessing buffer)
209     (error (mixi-message "Continuously accessing")))
210   (if (not (string-match mixi-message-continuously-accessing buffer))
211       buffer
212     (message (mixi-message "Waiting for continuously accessing..."))
213     (sit-for mixi-continuously-access-interval)
214     (funcall mixi-retrieve-function url post-data)))
215
216 (defun mixi-w3-retrieve (url &optional post-data)
217   "Retrieve the URL and return gotten strings."
218   (if post-data
219       (progn
220         (setq url-request-method "POST")
221         (setq url-request-data post-data))
222     (setq url-request-method "GET")
223     (setq url-request-data nil))
224   (let* ((url (url-expand-file-name url mixi-url))
225          (buffer (url-retrieve-synchronously url))
226          ret)
227     (unless (bufferp buffer)
228       (error (mixi-message "Cannot retrieve")))
229     (with-current-buffer buffer
230       (goto-char (point-min))
231       (if (re-search-forward "HTTP/[0-9.]+ 302 Moved" nil t)
232           (if (re-search-forward
233                (concat "Location: " mixi-url "\\(.+\\)") nil t)
234               (setq ret (mixi-w3-retrieve (match-string 1) post-data))
235             (setq ret (mixi-w3-retrieve "/home.pl" post-data)))
236         (unless (re-search-forward "HTTP/[0-9.]+ 200 OK" nil t)
237           (error (mixi-message "Cannot retrieve")))
238         (search-forward "\n\n")
239         (setq ret (mm-decode-coding-string
240                    (buffer-substring-no-properties (point) (point-max))
241                    mixi-coding-system))
242         (kill-buffer buffer)
243         (setq ret (mixi-retrieve-1 ret url post-data))))
244     ret))
245
246 (defun mixi-w3m-retrieve (url &optional post-data)
247   "Retrieve the URL and return gotten strings."
248   (let ((url (w3m-expand-url url mixi-url)))
249     (with-temp-buffer
250       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
251           (error (mixi-message "Cannot retrieve"))
252         (w3m-decode-buffer url)
253         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
254           (mixi-retrieve-1 ret url post-data))))))
255
256 (defun mixi-curl-retrieve (url &optional post-data)
257   "Retrieve the URL and return gotten strings."
258   (with-temp-buffer
259     (if (fboundp 'set-buffer-multibyte)
260         (set-buffer-multibyte nil))
261     (let ((orig-mode (default-file-modes))
262           (coding-system-for-read 'binary)
263           (coding-system-for-write 'binary)
264           process ret)
265       (unwind-protect
266           (progn
267             (set-default-file-modes 448)
268             (setq process
269                   (apply #'start-process "curl" (current-buffer)
270                          mixi-curl-program
271                          (append (if post-data '("-d" "@-"))
272                                  (list "-i" "-L" "-s"
273                                        "-b" mixi-curl-cookie-file
274                                        "-c" mixi-curl-cookie-file
275                                        (concat mixi-url url)))))
276             (set-process-sentinel process #'ignore))
277         (set-default-file-modes orig-mode))
278       (when post-data
279         (process-send-string process (concat post-data "\n"))
280         (process-send-eof process))
281       (while (eq (process-status process) 'run)
282         (accept-process-output process 1))
283       (goto-char (point-min))
284       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
285         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
286       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
287         (error (mixi-message "Cannot retrieve")))
288       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
289       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
290       (mixi-retrieve-1 ret url post-data))))
291
292 (defconst mixi-my-id-regexp
293   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
294
295 (defun mixi-login (&optional email password)
296   "Login to mixi."
297   (when (and (eq mixi-retrieve-function 'mixi-w3m-retrieve)
298              (not w3m-use-cookies))
299     (error
300      (mixi-message
301       "Require to accept cookies.  Please set `w3m-use-cookies' to t.")))
302   (let ((email (or email mixi-default-email
303                    (read-from-minibuffer (mixi-message "Login Email: "))))
304         (password (or password mixi-default-password
305                       (read-passwd (mixi-message "Login Password: ")))))
306     (let ((buffer (funcall mixi-retrieve-function "/login.pl"
307                            (concat "email=" email
308                                    "&password=" password
309                                    "&next_url=/home.pl"
310                                    "&sticky=on"))))
311       (unless (string-match "url=/check\\.pl\\?n=" buffer)
312         (error (mixi-message "Cannot login")))
313       (setq buffer (funcall mixi-retrieve-function "/check.pl?n=home.pl"))
314       (if (string-match mixi-my-id-regexp buffer)
315           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
316         (error (mixi-message "Cannot login"))))))
317
318 (defun mixi-logout ()
319   (funcall mixi-retrieve-function "/logout.pl"))
320
321 (defmacro with-mixi-retrieve (url &rest body)
322   `(let (buffer)
323      (when ,url
324        (setq buffer (funcall mixi-retrieve-function ,url))
325        (when (string-match "login.pl" buffer)
326          (mixi-login)
327          (setq buffer (funcall mixi-retrieve-function ,url))))
328      ,@body))
329 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
330
331 (defun mixi-get-matched-items (url max-numbers regexp)
332   "Get matched items to REGEXP in URL."
333   (let ((page 1)
334         ids)
335     (catch 'end
336       (while (or (null max-numbers) (< (length ids) max-numbers))
337         (with-mixi-retrieve (format url page)
338           (let ((pos 0))
339             (while (and (string-match regexp buffer pos)
340                         (or (null max-numbers) (< (length ids) max-numbers)))
341               (let ((num 1)
342                     list)
343                 (while (match-string num buffer)
344                   (setq list (cons (match-string num buffer) list))
345                   (incf num))
346                 (when (member (reverse list) ids)
347                   (throw 'end ids))
348                 (setq ids (cons (reverse list) ids))
349                 (setq pos (match-end (1- num)))))
350             (when (eq pos 0)
351               (throw 'end ids))))
352         (incf page)))
353     ;; FIXME: Sort? Now order by newest.
354     (reverse ids)))
355
356 ;; stolen (and modified) from shimbun.el
357 (defun mixi-remove-markup (string)
358   "Remove markups from STRING."
359   (with-temp-buffer
360     (insert string)
361     (save-excursion
362       (goto-char (point-min))
363       (while (search-forward "<!--" nil t)
364         (delete-region (match-beginning 0)
365                        (or (search-forward "-->" nil t)
366                            (point-max))))
367       (goto-char (point-min))
368       (while (re-search-forward "<[^>]+>" nil t)
369         (replace-match "" t t))
370       (goto-char (point-min))
371       (while (re-search-forward "\r" nil t)
372         (replace-match "\n" t t)))
373     (buffer-string)))
374
375 ;; Cache.
376
377 ;; stolen from time-date.el
378 (defmacro with-mixi-decoded-time-value (varlist &rest body)
379   "Decode a time value and bind it according to VARLIST, then eval BODY.
380
381 The value of the last form in BODY is returned.
382
383 Each element of the list VARLIST is a list of the form
384 \(HIGH-SYMBOL LOW-SYMBOL MICRO-SYMBOL [TYPE-SYMBOL] TIME-VALUE).
385 The time value TIME-VALUE is decoded and the result it bound to
386 the symbols HIGH-SYMBOL, LOW-SYMBOL and MICRO-SYMBOL.
387
388 The optional TYPE-SYMBOL is bound to the type of the time value.
389 Type 0 is the cons cell (HIGH . LOW), type 1 is the list (HIGH
390 LOW), and type 3 is the list (HIGH LOW MICRO)."
391   (declare (indent 1)
392            (debug ((&rest (symbolp symbolp symbolp &or [symbolp form] form))
393                    body)))
394   (if varlist
395       (let* ((elt (pop varlist))
396              (high (pop elt))
397              (low (pop elt))
398              (micro (pop elt))
399              (type (unless (eq (length elt) 1)
400                      (pop elt)))
401              (time-value (car elt))
402              (gensym (make-symbol "time")))
403         `(let* ,(append `((,gensym ,time-value)
404                           (,high (pop ,gensym))
405                           ,low ,micro)
406                         (when type `(,type)))
407            (if (consp ,gensym)
408                (progn
409                  (setq ,low (pop ,gensym))
410                  (if ,gensym
411                      ,(append `(setq ,micro (car ,gensym))
412                               (when type `(,type 2)))
413                    ,(append `(setq ,micro 0)
414                             (when type `(,type 1)))))
415              ,(append `(setq ,low ,gensym ,micro 0)
416                       (when type `(,type 0))))
417            (with-mixi-decoded-time-value ,varlist ,@body)))
418     `(progn ,@body)))
419 (put 'with-mixi-decoded-time-value 'lisp-indent-function 'defun)
420
421 ;; stolen from time-date.el
422 (defun mixi-encode-time-value (high low micro type)
423   "Encode HIGH, LOW, and MICRO into a time value of type TYPE.
424 Type 0 is the cons cell (HIGH . LOW), type 1 is the list (HIGH LOW),
425 and type 3 is the list (HIGH LOW MICRO)."
426   (cond
427    ((eq type 0) (cons high low))
428    ((eq type 1) (list high low))
429    ((eq type 2) (list high low micro))))
430
431 ;; stolen from time-date.el
432 (defun mixi-time-less-p (t1 t2)
433   "Say whether time value T1 is less than time value T2."
434   (with-mixi-decoded-time-value ((high1 low1 micro1 t1)
435                                  (high2 low2 micro2 t2))
436     (or (< high1 high2)
437         (and (= high1 high2)
438              (or (< low1 low2)
439                  (and (= low1 low2)
440                       (< micro1 micro2)))))))
441
442 ;; stolen from time-date.el
443 (defun mixi-time-add (t1 t2)
444   "Add two time values.  One should represent a time difference."
445   (with-mixi-decoded-time-value ((high low micro type t1)
446                                  (high2 low2 micro2 type2 t2))
447     (setq high (+ high high2)
448           low (+ low low2)
449           micro (+ micro micro2)
450           type (max type type2))
451     (when (>= micro 1000000)
452       (setq low (1+ low)
453             micro (- micro 1000000)))
454     (when (>= low 65536)
455       (setq high (1+ high)
456             low (- low 65536)))
457     (mixi-encode-time-value high low micro type)))
458
459 ;; stolen from time-date.el
460 (defun mixi-seconds-to-time (seconds)
461   "Convert SECONDS (a floating point number) to a time value."
462   (list (floor seconds 65536)
463         (floor (mod seconds 65536))
464         (floor (* (- seconds (ffloor seconds)) 1000000))))
465
466 (defun mixi-cache-expired-p (object)
467   "Whether a cache of OBJECT is expired."
468   ;; FIXME: Use method instead of `(aref (cdr object) 0)'.
469   (let ((timestamp (aref (cdr object) 0)))
470     (unless (or (null mixi-cache-expires)
471                  (null timestamp))
472       (mixi-time-less-p
473        (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
474        (current-time)))))
475
476 (defun mixi-make-cache (key value table)
477   "Make a cache object and return it."
478   (let ((cache (gethash key table)))
479     (if (and cache (not (mixi-cache-expired-p cache)))
480         cache
481       (puthash key value table))))
482
483 ;; Object.
484 (defconst mixi-object-prefix "mixi-")
485
486 (defmacro mixi-object-class (object)
487   `(car-safe ,object))
488
489 (defmacro mixi-object-p (object)
490   `(eq (string-match (concat "^" mixi-object-prefix)
491                      (symbol-name (mixi-object-class ,object))) 0))
492
493 (defun mixi-object-name (object)
494   "Return the name of OBJECT."
495   (unless (mixi-object-p object)
496     (signal 'wrong-type-argument (list 'mixi-object-p object)))
497   (let ((class (mixi-object-class object)))
498     (substring (symbol-name class) (length mixi-object-prefix))))
499
500 (defun mixi-object-id (object)
501   "Return the id of OBJECT."
502   (unless (mixi-object-p object)
503     (signal 'wrong-type-argument (list 'mixi-object-p object)))
504   (let ((func (intern (concat mixi-object-prefix
505                               (mixi-object-name object) "-id"))))
506     (funcall func object)))
507
508 ;; Friend object.
509 (defvar mixi-friend-cache (make-hash-table :test 'equal))
510 (defun mixi-make-friend (id &optional nick)
511   "Return a friend object."
512   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick nil nil nil nil
513                                                  nil nil nil nil nil nil nil))
514                    mixi-friend-cache))
515
516 (defun mixi-make-me ()
517   (unless mixi-me
518     (with-mixi-retrieve "/home.pl"
519       (if (string-match mixi-my-id-regexp buffer)
520           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
521         (signal 'error (list 'who-am-i)))))
522   mixi-me)
523
524 (defmacro mixi-friend-p (friend)
525   `(eq (mixi-object-class ,friend) 'mixi-friend))
526
527 (defmacro mixi-friend-page (friend)
528   `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
529
530 (defconst mixi-friend-nick-regexp
531   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
532 (defconst mixi-friend-name-sex-regexp
533   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>\n+<td WIDTH=345>\\([^<]+\\) (\\([Ã˽÷]\\)À­)</td>")
534 (defconst mixi-friend-address-regexp
535   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
536 (defconst mixi-friend-age-regexp
537   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
538 (defconst mixi-friend-birthday-regexp
539   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
540 (defconst mixi-friend-blood-type-regexp
541   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
542 (defconst mixi-friend-birthplace-regexp
543   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
544 (defconst mixi-friend-hobby-regexp
545   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+\\)</td></tr>")
546 (defconst mixi-friend-job-regexp
547   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
548 (defconst mixi-friend-organization-regexp
549   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
550 (defconst mixi-friend-profile-regexp
551   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
552
553 (defun mixi-friend-realize (friend)
554   "Realize a FRIEND."
555   ;; FIXME: Check a expiration of cache?
556   (unless (mixi-friend-realize-p friend)
557     (let (buf)
558       (with-mixi-retrieve (mixi-friend-page friend)
559         (setq buf buffer))
560       (if (string-match mixi-friend-nick-regexp buf)
561           (mixi-friend-set-nick friend (match-string 1 buf))
562         (signal 'error (list 'cannot-find-nick friend)))
563       ;; For getting my profile.
564       (unless (string-match mixi-friend-name-sex-regexp buf)
565         (with-mixi-retrieve "/show_profile.pl"
566           (setq buf buffer)))
567       (if (string-match mixi-friend-name-sex-regexp buf)
568           (progn
569             (mixi-friend-set-name friend (match-string 2 buf))
570             (mixi-friend-set-sex friend
571                                  (if (string= (match-string 3 buf) "ÃË")
572                                      'male 'female)))
573         (signal 'error (list 'cannot-find-name-or-sex friend)))
574       (when (string-match mixi-friend-address-regexp buf)
575         (mixi-friend-set-address friend (match-string 1 buf)))
576       (when (string-match mixi-friend-age-regexp buf)
577         (mixi-friend-set-age
578          friend (string-to-number (match-string 2 buf))))
579       (when (string-match mixi-friend-birthday-regexp buf)
580         (mixi-friend-set-birthday
581          friend (list (string-to-number (match-string 1 buf))
582                       (string-to-number (match-string 2 buf)))))
583       (when (string-match mixi-friend-blood-type-regexp buf)
584         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
585       (when (string-match mixi-friend-birthplace-regexp buf)
586         (mixi-friend-set-birthplace friend (match-string 1 buf)))
587       (when (string-match mixi-friend-hobby-regexp buf)
588         (mixi-friend-set-hobby
589          friend (split-string (match-string 2 buf) ", ")))
590       (when (string-match mixi-friend-job-regexp buf)
591         (mixi-friend-set-job friend (match-string 2 buf)))
592       (when (string-match mixi-friend-organization-regexp buf)
593         (mixi-friend-set-organization friend (match-string 2 buf)))
594       (when (string-match mixi-friend-profile-regexp buf)
595         (mixi-friend-set-profile
596          friend (mixi-remove-markup (match-string 1 buf)))))
597     (mixi-friend-touch friend)))
598
599 (defun mixi-friend-realize-p (friend)
600   "Return the timestamp of FRIEND."
601   (unless (mixi-friend-p friend)
602     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
603   (aref (cdr friend) 0))
604
605 (defun mixi-friend-id (friend)
606   "Return the id of FRIEND."
607   (unless (mixi-friend-p friend)
608     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
609   (aref (cdr friend) 1))
610
611 (defun mixi-friend-nick (friend)
612   "Return the nick of FRIEND."
613   (unless (mixi-friend-p friend)
614     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
615   (unless (aref (cdr friend) 2)
616     (mixi-friend-realize friend))
617   (aref (cdr friend) 2))
618
619 (defun mixi-friend-name (friend)
620   "Return the name of FRIEND."
621   (unless (mixi-friend-p friend)
622     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
623   (mixi-friend-realize friend)
624   (aref (cdr friend) 3))
625
626 (defun mixi-friend-sex (friend)
627   "Return the sex of FRIEND."
628   (unless (mixi-friend-p friend)
629     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
630   (mixi-friend-realize friend)
631   (aref (cdr friend) 4))
632
633 (defun mixi-friend-address (friend)
634   "Return the address of FRIEND."
635   (unless (mixi-friend-p friend)
636     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
637   (mixi-friend-realize friend)
638   (aref (cdr friend) 5))
639
640 (defun mixi-friend-age (friend)
641   "Return the age of FRIEND."
642   (unless (mixi-friend-p friend)
643     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
644   (mixi-friend-realize friend)
645   (aref (cdr friend) 6))
646
647 (defun mixi-friend-birthday (friend)
648   "Return the birthday of FRIEND."
649   (unless (mixi-friend-p friend)
650     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
651   (mixi-friend-realize friend)
652   (aref (cdr friend) 7))
653
654 (defun mixi-friend-blood-type (friend)
655   "Return the blood type of FRIEND."
656   (unless (mixi-friend-p friend)
657     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
658   (mixi-friend-realize friend)
659   (aref (cdr friend) 8))
660
661 (defun mixi-friend-birthplace (friend)
662   "Return the birthplace of FRIEND."
663   (unless (mixi-friend-p friend)
664     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
665   (mixi-friend-realize friend)
666   (aref (cdr friend) 9))
667
668 (defun mixi-friend-hobby (friend)
669   "Return the hobby of FRIEND."
670   (unless (mixi-friend-p friend)
671     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
672   (mixi-friend-realize friend)
673   (aref (cdr friend) 10))
674
675 (defun mixi-friend-job (friend)
676   "Return the job of FRIEND."
677   (unless (mixi-friend-p friend)
678     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
679   (mixi-friend-realize friend)
680   (aref (cdr friend) 11))
681
682 (defun mixi-friend-organization (friend)
683   "Return the organization of FRIEND."
684   (unless (mixi-friend-p friend)
685     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
686   (mixi-friend-realize friend)
687   (aref (cdr friend) 12))
688
689 (defun mixi-friend-profile (friend)
690   "Return the pforile of FRIEND."
691   (unless (mixi-friend-p friend)
692     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
693   (mixi-friend-realize friend)
694   (aref (cdr friend) 13))
695
696 (defun mixi-friend-touch (friend)
697   "Set the timestamp of FRIEND."
698   (unless (mixi-friend-p friend)
699     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
700   (aset (cdr friend) 0 (current-time)))
701
702 (defun mixi-friend-set-nick (friend nick)
703   "Set the nick of FRIEND."
704   (unless (mixi-friend-p friend)
705     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
706   (aset (cdr friend) 2 nick))
707
708 (defun mixi-friend-set-name (friend name)
709   "Set the name of FRIEND."
710   (unless (mixi-friend-p friend)
711     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
712   (aset (cdr friend) 3 name))
713
714 (defun mixi-friend-set-sex (friend sex)
715   "Set the sex of FRIEND."
716   (unless (mixi-friend-p friend)
717     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
718   (aset (cdr friend) 4 sex))
719
720 (defun mixi-friend-set-address (friend address)
721   "Set the address of FRIEND."
722   (unless (mixi-friend-p friend)
723     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
724   (aset (cdr friend) 5 address))
725
726 (defun mixi-friend-set-age (friend age)
727   "Set the age of FRIEND."
728   (unless (mixi-friend-p friend)
729     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
730   (aset (cdr friend) 6 age))
731
732 (defun mixi-friend-set-birthday (friend birthday)
733   "Set the birthday of FRIEND."
734   (unless (mixi-friend-p friend)
735     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
736   (aset (cdr friend) 7 birthday))
737
738 (defun mixi-friend-set-blood-type (friend blood-type)
739   "Set the blood type of FRIEND."
740   (unless (mixi-friend-p friend)
741     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
742   (aset (cdr friend) 8 blood-type))
743
744 (defun mixi-friend-set-birthplace (friend birthplace)
745   "Set the birthplace of FRIEND."
746   (unless (mixi-friend-p friend)
747     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
748   (aset (cdr friend) 9 birthplace))
749
750 (defun mixi-friend-set-hobby (friend hobby)
751   "Set the hobby of FRIEND."
752   (unless (mixi-friend-p friend)
753     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
754   (aset (cdr friend) 10 hobby))
755
756 (defun mixi-friend-set-job (friend job)
757   "Set the job of FRIEND."
758   (unless (mixi-friend-p friend)
759     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
760   (aset (cdr friend) 11 job))
761
762 (defun mixi-friend-set-organization (friend organization)
763   "Set the organization of FRIEND."
764   (unless (mixi-friend-p friend)
765     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
766   (aset (cdr friend) 12 organization))
767
768 (defun mixi-friend-set-profile (friend profile)
769   "Set the profile of FRIEND."
770   (unless (mixi-friend-p friend)
771     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
772   (aset (cdr friend) 13 profile))
773
774 (defmacro mixi-friend-list-page (&optional friend)
775   `(concat "/list_friend.pl?page=%d"
776            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
777
778 (defconst mixi-friend-list-id-regexp
779   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
780 (defconst mixi-friend-list-nick-regexp
781   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
782
783 (defun mixi-get-friends (&rest args)
784   "Get friends of FRIEND."
785   (when (> (length args) 2)
786     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
787   (let ((friend (nth 0 args))
788         (max-numbers (nth 1 args)))
789     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
790       (setq friend (nth 1 args))
791       (setq max-numbers (nth 0 args)))
792     (unless (or (null friend) (mixi-friend-p friend))
793       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
794     (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
795                                        max-numbers
796                                        mixi-friend-list-id-regexp))
797           (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
798                                          max-numbers
799                                          mixi-friend-list-nick-regexp)))
800       (let ((index 0)
801             ret)
802         (while (< index (length ids))
803           (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
804                                             (nth 0 (nth index nicks))) ret))
805           (incf index))
806         (reverse ret)))))
807
808 ;; Favorite.
809 (defmacro mixi-favorite-list-page ()
810   `(concat "/list_bookmark.pl?page=%d"))
811
812 (defconst mixi-favorite-list-id-regexp
813   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
814 (defconst mixi-favorite-list-nick-regexp
815   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
816 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
817
818 (defun mixi-get-favorites (&optional max-numbers)
819   "Get favorites."
820   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
821                                      max-numbers
822                                      mixi-favorite-list-id-regexp))
823         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
824                                        max-numbers
825                                        mixi-favorite-list-nick-regexp)))
826     (let ((index 0)
827           ret)
828       (while (< index (length ids))
829         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
830                                           (nth 0 (nth index nicks))) ret))
831         (incf index))
832       (reverse ret))))
833
834 ;; Log object.
835 (defun mixi-make-log (friend time)
836   "Return a log object."
837   (cons 'mixi-log (vector friend time)))
838
839 (defmacro mixi-log-p (log)
840   `(eq (mixi-object-class ,log) 'mixi-log))
841
842 (defun mixi-log-friend (log)
843   "Return the friend of LOG."
844   (unless (mixi-log-p log)
845     (signal 'wrong-type-argument (list 'mixi-log-p log)))
846   (aref (cdr log) 0))
847
848 (defun mixi-log-time (log)
849   "Return the time of LOG."
850   (unless (mixi-log-p log)
851     (signal 'wrong-type-argument (list 'mixi-log-p log)))
852   (aref (cdr log) 1))
853
854 (defmacro mixi-log-list-page ()
855   `(concat "/show_log.pl"))
856
857 (defconst mixi-log-list-regexp
858   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a><br>")
859
860 (defun mixi-get-logs (&optional max-numbers)
861   "Get logs."
862   (let ((items (mixi-get-matched-items (mixi-log-list-page)
863                                        max-numbers
864                                        mixi-log-list-regexp)))
865     (mapcar (lambda (item)
866               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
867                              (encode-time 0
868                                           (string-to-number (nth 4 item))
869                                           (string-to-number (nth 3 item))
870                                           (string-to-number (nth 2 item))
871                                           (string-to-number (nth 1 item))
872                                           (string-to-number (nth 0 item)))))
873             items)))
874
875 ;; Diary object.
876 (defvar mixi-diary-cache (make-hash-table :test 'equal))
877 (defun mixi-make-diary (owner id)
878   "Return a diary object."
879   (let ((owner (or owner (mixi-make-me))))
880     (mixi-make-cache (list (mixi-friend-id owner) id)
881                      (cons 'mixi-diary (vector nil owner id nil nil nil))
882                      mixi-diary-cache)))
883
884 (defmacro mixi-diary-p (diary)
885   `(eq (mixi-object-class ,diary) 'mixi-diary))
886
887 (defmacro mixi-diary-page (diary)
888   `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
889            "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
890
891 ;; FIXME: Remove `¤µ¤ó'.
892 (defconst mixi-diary-owner-nick-regexp
893   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
894 (defconst mixi-diary-time-regexp
895   "<td ALIGN=center ROWSPAN=2 NOWRAP WIDTH=95 bgcolor=#FFD8B0>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
896 (defconst mixi-diary-title-regexp
897   "<td BGCOLOR=#FFF4E0 WIDTH=430>&nbsp;\\([^<]+\\)</td></tr>")
898 (defconst mixi-diary-content-regexp
899   "<td CLASS=h12>\\(.+\\)</td></tr>")
900
901 (defun mixi-diary-realize (diary)
902   "Realize a DIARY."
903   ;; FIXME: Check a expiration of cache?
904   (unless (mixi-diary-realize-p diary)
905     (with-mixi-retrieve (mixi-diary-page diary)
906       (if (string-match mixi-diary-owner-nick-regexp buffer)
907           (mixi-friend-set-nick (mixi-diary-owner diary)
908                                 (match-string 1 buffer))
909         (signal 'error (list 'cannot-find-owner-nick diary)))
910       (if (string-match mixi-diary-time-regexp buffer)
911           (mixi-diary-set-time
912            diary (encode-time 0 (string-to-number (match-string 5 buffer))
913                               (string-to-number (match-string 4 buffer))
914                               (string-to-number (match-string 3 buffer))
915                               (string-to-number (match-string 2 buffer))
916                               (string-to-number (match-string 1 buffer))))
917         (signal 'error (list 'cannot-find-time diary)))
918       (if (string-match mixi-diary-title-regexp buffer)
919           (mixi-diary-set-title diary (match-string 1 buffer))
920         (signal 'error (list 'cannot-find-title diary)))
921       (if (string-match mixi-diary-content-regexp buffer)
922           (mixi-diary-set-content diary (mixi-remove-markup
923                                          (match-string 1 buffer)))
924         (signal 'error (list 'cannot-find-content diary))))
925     (mixi-diary-touch diary)))
926
927 (defun mixi-diary-realize-p (diary)
928   "Return the timestamp of DIARY."
929   (unless (mixi-diary-p diary)
930     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
931   (aref (cdr diary) 0))
932
933 (defun mixi-diary-owner (diary)
934   "Return the owner of DIARY."
935   (unless (mixi-diary-p diary)
936     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
937   (aref (cdr diary) 1))
938
939 (defun mixi-diary-id (diary)
940   "Return the id of DIARY."
941   (unless (mixi-diary-p diary)
942     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
943   (aref (cdr diary) 2))
944
945 (defun mixi-diary-time (diary)
946   "Return the time of DIARY."
947   (unless (mixi-diary-p diary)
948     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
949   (mixi-diary-realize diary)
950   (aref (cdr diary) 3))
951
952 (defun mixi-diary-title (diary)
953   "Return the title of DIARY."
954   (unless (mixi-diary-p diary)
955     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
956   (mixi-diary-realize diary)
957   (aref (cdr diary) 4))
958
959 (defun mixi-diary-content (diary)
960   "Return the content of DIARY."
961   (unless (mixi-diary-p diary)
962     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
963   (mixi-diary-realize diary)
964   (aref (cdr diary) 5))
965
966 (defun mixi-diary-touch (diary)
967   "Set the timestamp of DIARY."
968   (unless (mixi-diary-p diary)
969     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
970   (aset (cdr diary) 0 (current-time)))
971
972 (defun mixi-diary-set-time (diary time)
973   "Set the time of DIARY."
974   (unless (mixi-diary-p diary)
975     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
976   (aset (cdr diary) 3 time))
977
978 (defun mixi-diary-set-title (diary title)
979   "Set the title of DIARY."
980   (unless (mixi-diary-p diary)
981     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
982   (aset (cdr diary) 4 title))
983
984 (defun mixi-diary-set-content (diary content)
985   "Set the content of DIARY."
986   (unless (mixi-diary-p diary)
987     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
988   (aset (cdr diary) 5 content))
989
990 (defmacro mixi-diary-list-page (&optional friend)
991   `(concat "/list_diary.pl?page=%d"
992            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
993
994 (defconst mixi-diary-list-regexp
995   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
996
997 (defun mixi-get-diaries (&rest args)
998   "Get diaries of FRIEND."
999   (when (> (length args) 2)
1000     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
1001   (let ((friend (nth 0 args))
1002         (max-numbers (nth 1 args)))
1003     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
1004       (setq friend (nth 1 args))
1005       (setq max-numbers (nth 0 args)))
1006     (unless (or (null friend) (mixi-friend-p friend))
1007       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1008     (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
1009                                          max-numbers
1010                                          mixi-diary-list-regexp)))
1011       (mapcar (lambda (item)
1012                 (mixi-make-diary friend (nth 0 item)))
1013               items))))
1014
1015 (defmacro mixi-new-diary-list-page ()
1016   `(concat "/new_friend_diary.pl?page=%d"))
1017
1018 (defconst mixi-new-diary-list-regexp
1019   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
1020
1021 (defun mixi-get-new-diaries (&optional max-numbers)
1022   "Get new diaries."
1023   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
1024                                        max-numbers
1025                                        mixi-new-diary-list-regexp)))
1026     (mapcar (lambda (item)
1027               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
1028             items)))
1029
1030 ;; Community object.
1031 (defvar mixi-community-cache (make-hash-table :test 'equal))
1032 (defun mixi-make-community (id &optional name)
1033   "Return a community object."
1034   (mixi-make-cache id (cons 'mixi-community (vector nil id name nil nil nil
1035                                                     nil nil nil nil))
1036                    mixi-community-cache))
1037
1038 (defmacro mixi-community-p (community)
1039   `(eq (mixi-object-class ,community) 'mixi-community))
1040
1041 (defmacro mixi-community-page (community)
1042   `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
1043
1044 (defconst mixi-community-nodata-regexp
1045   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1046 (defconst mixi-community-name-regexp
1047   "<td WIDTH=345>\\(.*\\)</td></tr>")
1048 (defconst mixi-community-birthday-regexp
1049   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>³«ÀßÆü</font></td>\n<td>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1050 ;; FIXME: Care when the owner has seceded.
1051 (defconst mixi-community-owner-regexp
1052   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>´ÉÍý¿Í</font></td>\n<td>\n\n<a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\n\\(.+\\)</a>")
1053 (defconst mixi-community-category-regexp
1054   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\n<td>\\([^<]+\\)</td>")
1055 (defconst mixi-community-members-regexp
1056   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\n<td>\\([0-9]+\\)¿Í</td></tr>")
1057 (defconst mixi-community-open-level-regexp
1058   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>
1059 <td>\\(.+\\)</td></tr>")
1060 (defconst mixi-community-authority-regexp
1061   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\n<td>\\(.+\\)</td></tr>")
1062 (defconst mixi-community-description-regexp
1063   "<td CLASS=h120>\\(.+\\)</td>")
1064
1065 (defun mixi-community-realize (community)
1066   "Realize a COMMUNITY."
1067   ;; FIXME: Check a expiration of cache?
1068   (unless (mixi-community-realize-p community)
1069     (with-mixi-retrieve (mixi-community-page community)
1070       (if (string-match mixi-community-nodata-regexp buffer)
1071           ;; FIXME: Set all members?
1072           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1073         (if (string-match mixi-community-name-regexp buffer)
1074             (mixi-community-set-name community (match-string 1 buffer))
1075           (signal 'error (list 'cannot-find-name community)))
1076         (if (string-match mixi-community-birthday-regexp buffer)
1077             (mixi-community-set-birthday
1078              community
1079              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
1080                           (string-to-number (match-string 2 buffer))
1081                           (string-to-number (match-string 1 buffer))))
1082           (signal 'error (list 'cannot-find-birthday community)))
1083         (if (string-match mixi-community-owner-regexp buffer)
1084             (if (string= (match-string 1 buffer) "home.pl")
1085                 (mixi-community-set-owner community (mixi-make-me))
1086               (mixi-community-set-owner
1087                community (mixi-make-friend (match-string 2 buffer)
1088                                            (match-string 3 buffer))))
1089           (signal 'error (list 'cannot-find-owner community)))
1090         (if (string-match mixi-community-category-regexp buffer)
1091             (mixi-community-set-category community (match-string 1 buffer))
1092           (signal 'error (list 'cannot-find-category community)))
1093         (if (string-match mixi-community-members-regexp buffer)
1094             (mixi-community-set-members
1095              community (string-to-number (match-string 1 buffer)))
1096           (signal 'error (list 'cannot-find-members community)))
1097         (if (string-match mixi-community-open-level-regexp buffer)
1098             (mixi-community-set-open-level community (match-string 1 buffer))
1099           (signal 'error (list 'cannot-find-open-level community)))
1100         (if (string-match mixi-community-authority-regexp buffer)
1101             (mixi-community-set-authority community (match-string 1 buffer))
1102           (signal 'error (list 'cannot-find-authority community)))
1103         (if (string-match mixi-community-description-regexp buffer)
1104             (mixi-community-set-description
1105              community (mixi-remove-markup (match-string 1 buffer)))
1106           (signal 'error (list 'cannot-find-description community)))))
1107     (mixi-community-touch community)))
1108
1109 (defun mixi-community-realize-p (community)
1110   "Return the timestamp of COMMUNITY."
1111   (unless (mixi-community-p community)
1112     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1113   (aref (cdr community) 0))
1114
1115 (defun mixi-community-id (community)
1116   "Return the id of COMMUNITY."
1117   (unless (mixi-community-p community)
1118     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1119   (aref (cdr community) 1))
1120
1121 (defun mixi-community-name (community)
1122   "Return the name of COMMUNITY."
1123   (unless (mixi-community-p community)
1124     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1125   (unless (aref (cdr community) 2)
1126     (mixi-community-realize community))
1127   (aref (cdr community) 2))
1128
1129 (defun mixi-community-birthday (community)
1130   "Return the birthday of COMMUNITY."
1131   (unless (mixi-community-p community)
1132     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1133   (mixi-community-realize community)
1134   (aref (cdr community) 3))
1135
1136 (defun mixi-community-owner (community)
1137   "Return the owner of COMMUNITY."
1138   (unless (mixi-community-p community)
1139     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1140   (mixi-community-realize community)
1141   (aref (cdr community) 4))
1142
1143 (defun mixi-community-category (community)
1144   "Return the category of COMMUNITY."
1145   (unless (mixi-community-p community)
1146     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1147   (mixi-community-realize community)
1148   (aref (cdr community) 5))
1149
1150 (defun mixi-community-members (community)
1151   "Return the members of COMMUNITY."
1152   (unless (mixi-community-p community)
1153     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1154   (mixi-community-realize community)
1155   (aref (cdr community) 6))
1156
1157 (defun mixi-community-open-level (community)
1158   "Return the open-level of COMMUNITY."
1159   (unless (mixi-community-p community)
1160     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1161   (mixi-community-realize community)
1162   (aref (cdr community) 7))
1163
1164 (defun mixi-community-authority (community)
1165   "Return the authority of COMMUNITY."
1166   (unless (mixi-community-p community)
1167     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1168   (mixi-community-realize community)
1169   (aref (cdr community) 8))
1170
1171 (defun mixi-community-description (community)
1172   "Return the description of COMMUNITY."
1173   (unless (mixi-community-p community)
1174     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1175   (mixi-community-realize community)
1176   (aref (cdr community) 9))
1177
1178 (defun mixi-community-touch (community)
1179   "Set the timestamp of COMMUNITY."
1180   (unless (mixi-community-p community)
1181     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1182   (aset (cdr community) 0 (current-time)))
1183
1184 (defun mixi-community-set-name (community name)
1185   "Set the name of COMMUNITY."
1186   (unless (mixi-community-p community)
1187     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1188   (aset (cdr community) 2 name))
1189
1190 (defun mixi-community-set-birthday (community birthday)
1191   "Set the birthday of COMMUNITY."
1192   (unless (mixi-community-p community)
1193     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1194   (aset (cdr community) 3 birthday))
1195
1196 (defun mixi-community-set-owner (community owner)
1197   "Set the owner of COMMUNITY."
1198   (unless (mixi-community-p community)
1199     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1200   (unless (mixi-friend-p owner)
1201     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1202   (aset (cdr community) 4 owner))
1203
1204 (defun mixi-community-set-category (community category)
1205   "Set the category of COMMUNITY."
1206   (unless (mixi-community-p community)
1207     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1208   (aset (cdr community) 5 category))
1209
1210 (defun mixi-community-set-members (community members)
1211   "Set the name of COMMUNITY."
1212   (unless (mixi-community-p community)
1213     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1214   (aset (cdr community) 6 members))
1215
1216 (defun mixi-community-set-open-level (community open-level)
1217   "Set the name of COMMUNITY."
1218   (unless (mixi-community-p community)
1219     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1220   (aset (cdr community) 7 open-level))
1221
1222 (defun mixi-community-set-authority (community authority)
1223   "Set the name of COMMUNITY."
1224   (unless (mixi-community-p community)
1225     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1226   (aset (cdr community) 8 authority))
1227
1228 (defun mixi-community-set-description (community description)
1229   "Set the name of COMMUNITY."
1230   (unless (mixi-community-p community)
1231     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1232   (aset (cdr community) 9 description))
1233
1234 (defmacro mixi-community-list-page (&optional friend)
1235   `(concat "/list_community.pl?page=%d"
1236            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1237
1238 (defconst mixi-community-list-id-regexp
1239   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1240 (defconst mixi-community-list-name-regexp
1241   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1242
1243 (defun mixi-get-communities (&rest args)
1244   "Get communities of FRIEND."
1245   (when (> (length args) 2)
1246     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
1247   (let ((friend (nth 0 args))
1248         (max-numbers (nth 1 args)))
1249     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
1250       (setq friend (nth 1 args))
1251       (setq max-numbers (nth 0 args)))
1252     (unless (or (null friend) (mixi-friend-p friend))
1253       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1254     (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1255                                        max-numbers
1256                                        mixi-community-list-id-regexp))
1257           (names (mixi-get-matched-items (mixi-community-list-page friend)
1258                                          max-numbers
1259                                          mixi-community-list-name-regexp)))
1260       (let ((index 0)
1261             ret)
1262         (while (< index (length ids))
1263           (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1264                                                (nth 0 (nth index names))) ret))
1265           (incf index))
1266         (reverse ret)))))
1267
1268 ;; Topic object.
1269 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1270 (defun mixi-make-topic (community id)
1271   "Return a topic object."
1272   (mixi-make-cache (list (mixi-community-id community) id)
1273                    (cons 'mixi-topic (vector nil community id nil nil nil nil))
1274                    mixi-topic-cache))
1275
1276 (defmacro mixi-topic-p (topic)
1277   `(eq (mixi-object-class ,topic) 'mixi-topic))
1278
1279 (defmacro mixi-topic-page (topic)
1280   `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
1281            "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
1282
1283 (defconst mixi-topic-time-regexp
1284   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1285 (defconst mixi-topic-title-regexp
1286   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1287 ;; FIXME: Remove `¤µ¤ó'.
1288 (defconst mixi-topic-owner-regexp
1289   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)\\(¤µ¤ó\\)?</a>")
1290 (defconst mixi-topic-content-regexp
1291   "<td class=\"h120\"><table><tr>\\(.+\\)?</tr></table>\\(.+\\)</td>")
1292
1293 (defun mixi-topic-realize (topic)
1294   "Realize a TOPIC."
1295   ;; FIXME: Check a expiration of cache?
1296   (unless (mixi-topic-realize-p topic)
1297     (with-mixi-retrieve (mixi-topic-page topic)
1298       (if (string-match mixi-topic-time-regexp buffer)
1299           (mixi-topic-set-time
1300            topic (encode-time 0 (string-to-number (match-string 5 buffer))
1301                               (string-to-number (match-string 4 buffer))
1302                               (string-to-number (match-string 3 buffer))
1303                               (string-to-number (match-string 2 buffer))
1304                               (string-to-number (match-string 1 buffer))))
1305         (signal 'error (list 'cannot-find-time topic)))
1306       (if (string-match mixi-topic-title-regexp buffer)
1307           (mixi-topic-set-title topic (match-string 1 buffer))
1308         (signal 'error (list 'cannot-find-title topic)))
1309       (if (string-match mixi-topic-owner-regexp buffer)
1310           (mixi-topic-set-owner topic
1311                                 (mixi-make-friend (match-string 1 buffer)
1312                                                   (match-string 2 buffer)))
1313         (signal 'error (list 'cannot-find-owner topic)))
1314       (if (string-match mixi-topic-content-regexp buffer)
1315           (mixi-topic-set-content topic (mixi-remove-markup
1316                                          (match-string 2 buffer)))
1317         (signal 'error (list 'cannot-find-content topic))))
1318     (mixi-topic-touch topic)))
1319
1320 (defun mixi-topic-realize-p (topic)
1321   "Return the timestamp of TOPIC."
1322   (unless (mixi-topic-p topic)
1323     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1324   (aref (cdr topic) 0))
1325
1326 (defun mixi-topic-community (topic)
1327   "Return the community of TOPIC."
1328   (unless (mixi-topic-p topic)
1329     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1330   (aref (cdr topic) 1))
1331
1332 (defun mixi-topic-id (topic)
1333   "Return the id of TOPIC."
1334   (unless (mixi-topic-p topic)
1335     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1336   (aref (cdr topic) 2))
1337
1338 (defun mixi-topic-time (topic)
1339   "Return the time of TOPIC."
1340   (unless (mixi-topic-p topic)
1341     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1342   (mixi-topic-realize topic)
1343   (aref (cdr topic) 3))
1344
1345 (defun mixi-topic-title (topic)
1346   "Return the title of TOPIC."
1347   (unless (mixi-topic-p topic)
1348     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1349   (mixi-topic-realize topic)
1350   (aref (cdr topic) 4))
1351
1352 (defun mixi-topic-owner (topic)
1353   "Return the owner of TOPIC."
1354   (unless (mixi-topic-p topic)
1355     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1356   (mixi-topic-realize topic)
1357   (aref (cdr topic) 5))
1358
1359 (defun mixi-topic-content (topic)
1360   "Return the content of TOPIC."
1361   (unless (mixi-topic-p topic)
1362     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1363   (mixi-topic-realize topic)
1364   (aref (cdr topic) 6))
1365
1366 (defun mixi-topic-touch (topic)
1367   "Set the timestamp of TOPIC."
1368   (unless (mixi-topic-p topic)
1369     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1370   (aset (cdr topic) 0 (current-time)))
1371
1372 (defun mixi-topic-set-time (topic time)
1373   "Set the time of TOPIC."
1374   (unless (mixi-topic-p topic)
1375     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1376   (aset (cdr topic) 3 time))
1377
1378 (defun mixi-topic-set-title (topic title)
1379   "Set the title of TOPIC."
1380   (unless (mixi-topic-p topic)
1381     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1382   (aset (cdr topic) 4 title))
1383
1384 (defun mixi-topic-set-owner (topic owner)
1385   "Set the owner of TOPIC."
1386   (unless (mixi-topic-p topic)
1387     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1388   (unless (mixi-friend-p owner)
1389     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1390   (aset (cdr topic) 5 owner))
1391
1392 (defun mixi-topic-set-content (topic content)
1393   "Set the content of TOPIC."
1394   (unless (mixi-topic-p topic)
1395     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1396   (aset (cdr topic) 6 content))
1397
1398 (defmacro mixi-topic-list-page (community)
1399   `(concat "/list_bbs.pl?page=%d"
1400            "&id=" (mixi-community-id ,community)))
1401
1402 (defconst mixi-topic-list-regexp
1403   "<a href=view_bbs\\.pl\\?id=\\([0-9]+\\)")
1404
1405 (defun mixi-get-topics (community &optional max-numbers)
1406   "Get topics of COMMUNITY."
1407   (unless (mixi-community-p community)
1408     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1409   (let ((items (mixi-get-matched-items (mixi-topic-list-page community)
1410                                        max-numbers
1411                                        mixi-topic-list-regexp)))
1412     (mapcar (lambda (item)
1413               (mixi-make-topic community (nth 0 item)))
1414             items)))
1415
1416 (defmacro mixi-new-topic-list-page ()
1417   `(concat "/new_bbs.pl?page=%d"))
1418
1419 (defconst mixi-new-topic-list-regexp
1420   "<a href=\"view_bbs\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
1421
1422 (defun mixi-get-new-topics (&optional max-numbers)
1423   "Get new topics."
1424   (let ((items (mixi-get-matched-items (mixi-new-topic-list-page)
1425                                        max-numbers
1426                                        mixi-new-topic-list-regexp)))
1427     (mapcar (lambda (item)
1428               (mixi-make-topic (mixi-make-community (nth 1 item))
1429                                (nth 0 item)))
1430             items)))
1431
1432 ;; Comment object.
1433 (defun mixi-make-comment (parent owner time content)
1434   "Return a comment object."
1435   (cons 'mixi-comment (vector parent owner time content)))
1436
1437 (defmacro mixi-comment-p (comment)
1438   `(eq (mixi-object-class ,comment) 'mixi-comment))
1439
1440 (defun mixi-comment-parent (comment)
1441   "Return the parent of COMMENT."
1442   (unless (mixi-comment-p comment)
1443     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1444   (aref (cdr comment) 0))
1445
1446 (defun mixi-comment-owner (comment)
1447   "Return the owner of COMMENT."
1448   (unless (mixi-comment-p comment)
1449     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1450   (aref (cdr comment) 1))
1451
1452 (defun mixi-comment-time (comment)
1453   "Return the time of COMMENT."
1454   (unless (mixi-comment-p comment)
1455     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1456   (aref (cdr comment) 2))
1457
1458 (defun mixi-comment-content (comment)
1459   "Return the content of COMMENT."
1460   (unless (mixi-comment-p comment)
1461     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1462   (aref (cdr comment) 3))
1463
1464 (defun mixi-diary-comment-list-page (diary)
1465   (concat "/view_diary.pl?page=%d"
1466           "&id=" (mixi-diary-id diary)
1467           "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
1468
1469 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1470 (defconst mixi-diary-comment-list-regexp
1471 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
1472 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
1473 <input type=checkbox name=comment_id value=\".+\">
1474 \\|\\)
1475 </td>
1476 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
1477 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
1478 <tr>
1479 <td>
1480 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1481
1482 </td>
1483 </tr>
1484 </table>
1485 </td>
1486 </tr>
1487 <!-- ËÜʸ : start -->
1488 <tr>
1489 <td bgcolor=\"#ffffff\">
1490 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
1491 <tr>
1492 <td CLASS=h12>
1493 \\(.+\\)
1494 </td></tr></table>")
1495
1496 (defun mixi-topic-comment-list-page (topic)
1497   (concat "/view_bbs.pl?page=%d"
1498           "&id=" (mixi-topic-id topic)
1499           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
1500
1501 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1502 (defconst mixi-topic-comment-list-regexp
1503   "<tr valign=\"top\">
1504 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
1505 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1506 \\([0-9]+\\):\\([0-9]+\\)<br>
1507 \\(</td>\\)
1508 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
1509 <b>&nbsp;&nbsp;[0-9]+</b>:</font>&nbsp;
1510
1511 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1512
1513
1514
1515 </td>
1516 </tr>
1517 <tr>
1518 <td bgcolor=\"#ffffff\" align=\"center\">
1519 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
1520 <tr>
1521 <td class=\"h120\">
1522
1523 \\(.+\\)
1524 </td>
1525 </tr>
1526 </table>
1527 </td>
1528 </tr>")
1529
1530 (defun mixi-get-comments (parent &optional max-numbers)
1531   "Get comments of PARENT."
1532   (unless (mixi-object-p parent)
1533     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
1534   (let* ((name (mixi-object-name parent))
1535          (list-page (intern (concat mixi-object-prefix name
1536                                     "-comment-list-page")))
1537          (regexp (eval (intern (concat mixi-object-prefix name
1538                                        "-comment-list-regexp")))))
1539     (let ((items (mixi-get-matched-items
1540                   (funcall list-page parent) max-numbers regexp)))
1541       (mapcar (lambda (item)
1542                 (mixi-make-comment parent (mixi-make-friend
1543                                            (nth 6 item) (nth 7 item))
1544                                    (encode-time
1545                                     0
1546                                     (string-to-number (nth 4 item))
1547                                     (string-to-number (nth 3 item))
1548                                     (string-to-number (nth 2 item))
1549                                     (string-to-number (nth 1 item))
1550                                     (string-to-number (nth 0 item)))
1551                                    (mixi-remove-markup (nth 8 item))))
1552               items))))
1553
1554 (defmacro mixi-new-comment-list-page ()
1555   `(concat "/new_comment.pl?page=%d"))
1556
1557 (defconst mixi-new-comment-list-regexp
1558   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
1559
1560 (defun mixi-get-new-comments (&optional max-numbers)
1561   "Get new comments."
1562   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
1563                                        max-numbers
1564                                        mixi-new-comment-list-regexp)))
1565     (mapcar (lambda (item)
1566               (let ((diary (mixi-make-diary
1567                             (mixi-make-friend (nth 1 item))
1568                             (nth 0 item))))
1569                 (mixi-get-comments diary)))
1570             items)))
1571
1572 (provide 'mixi)
1573
1574 ;;; mixi.el ends here