* mixi.el (with-mixi-retrieve): Put `edebug-form-spec'.
[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 (put 'with-mixi-retrieve 'edebug-form-spec '(form body))
331
332 (defun mixi-get-matched-items (url max-numbers regexp)
333   "Get matched items to REGEXP in URL."
334   (let ((page 1)
335         ids)
336     (catch 'end
337       (while (or (null max-numbers) (< (length ids) max-numbers))
338         (with-mixi-retrieve (format url page)
339           (let ((pos 0))
340             (while (and (string-match regexp buffer pos)
341                         (or (null max-numbers) (< (length ids) max-numbers)))
342               (let ((num 1)
343                     list)
344                 (while (match-string num buffer)
345                   (setq list (cons (match-string num buffer) list))
346                   (incf num))
347                 (when (member (reverse list) ids)
348                   (throw 'end ids))
349                 (setq ids (cons (reverse list) ids))
350                 (setq pos (match-end (1- num)))))
351             (when (eq pos 0)
352               (throw 'end ids))))
353         (incf page)))
354     ;; FIXME: Sort? Now order by newest.
355     (reverse ids)))
356
357 ;; stolen (and modified) from shimbun.el
358 (defun mixi-remove-markup (string)
359   "Remove markups from STRING."
360   (with-temp-buffer
361     (insert string)
362     (save-excursion
363       (goto-char (point-min))
364       (while (search-forward "<!--" nil t)
365         (delete-region (match-beginning 0)
366                        (or (search-forward "-->" nil t)
367                            (point-max))))
368       (goto-char (point-min))
369       (while (re-search-forward "<[^>]+>" nil t)
370         (replace-match "" t t))
371       (goto-char (point-min))
372       (while (re-search-forward "\r" nil t)
373         (replace-match "\n" t t)))
374     (buffer-string)))
375
376 ;; Cache.
377
378 ;; stolen from time-date.el
379 (defmacro with-mixi-decoded-time-value (varlist &rest body)
380   "Decode a time value and bind it according to VARLIST, then eval BODY.
381
382 The value of the last form in BODY is returned.
383
384 Each element of the list VARLIST is a list of the form
385 \(HIGH-SYMBOL LOW-SYMBOL MICRO-SYMBOL [TYPE-SYMBOL] TIME-VALUE).
386 The time value TIME-VALUE is decoded and the result it bound to
387 the symbols HIGH-SYMBOL, LOW-SYMBOL and MICRO-SYMBOL.
388
389 The optional TYPE-SYMBOL is bound to the type of the time value.
390 Type 0 is the cons cell (HIGH . LOW), type 1 is the list (HIGH
391 LOW), and type 3 is the list (HIGH LOW MICRO)."
392   (declare (indent 1)
393            (debug ((&rest (symbolp symbolp symbolp &or [symbolp form] form))
394                    body)))
395   (if varlist
396       (let* ((elt (pop varlist))
397              (high (pop elt))
398              (low (pop elt))
399              (micro (pop elt))
400              (type (unless (eq (length elt) 1)
401                      (pop elt)))
402              (time-value (car elt))
403              (gensym (make-symbol "time")))
404         `(let* ,(append `((,gensym ,time-value)
405                           (,high (pop ,gensym))
406                           ,low ,micro)
407                         (when type `(,type)))
408            (if (consp ,gensym)
409                (progn
410                  (setq ,low (pop ,gensym))
411                  (if ,gensym
412                      ,(append `(setq ,micro (car ,gensym))
413                               (when type `(,type 2)))
414                    ,(append `(setq ,micro 0)
415                             (when type `(,type 1)))))
416              ,(append `(setq ,low ,gensym ,micro 0)
417                       (when type `(,type 0))))
418            (with-mixi-decoded-time-value ,varlist ,@body)))
419     `(progn ,@body)))
420 (put 'with-mixi-decoded-time-value 'lisp-indent-function 'defun)
421 (put 'with-mixi-decoded-time-value 'edebug-form-spec '(form body))
422
423 ;; stolen from time-date.el
424 (defun mixi-encode-time-value (high low micro type)
425   "Encode HIGH, LOW, and MICRO into a time value of type TYPE.
426 Type 0 is the cons cell (HIGH . LOW), type 1 is the list (HIGH LOW),
427 and type 3 is the list (HIGH LOW MICRO)."
428   (cond
429    ((eq type 0) (cons high low))
430    ((eq type 1) (list high low))
431    ((eq type 2) (list high low micro))))
432
433 ;; stolen from time-date.el
434 (defun mixi-time-less-p (t1 t2)
435   "Say whether time value T1 is less than time value T2."
436   (with-mixi-decoded-time-value ((high1 low1 micro1 t1)
437                                  (high2 low2 micro2 t2))
438     (or (< high1 high2)
439         (and (= high1 high2)
440              (or (< low1 low2)
441                  (and (= low1 low2)
442                       (< micro1 micro2)))))))
443
444 ;; stolen from time-date.el
445 (defun mixi-time-add (t1 t2)
446   "Add two time values.  One should represent a time difference."
447   (with-mixi-decoded-time-value ((high low micro type t1)
448                                  (high2 low2 micro2 type2 t2))
449     (setq high (+ high high2)
450           low (+ low low2)
451           micro (+ micro micro2)
452           type (max type type2))
453     (when (>= micro 1000000)
454       (setq low (1+ low)
455             micro (- micro 1000000)))
456     (when (>= low 65536)
457       (setq high (1+ high)
458             low (- low 65536)))
459     (mixi-encode-time-value high low micro type)))
460
461 ;; stolen from time-date.el
462 (defun mixi-seconds-to-time (seconds)
463   "Convert SECONDS (a floating point number) to a time value."
464   (list (floor seconds 65536)
465         (floor (mod seconds 65536))
466         (floor (* (- seconds (ffloor seconds)) 1000000))))
467
468 (defun mixi-cache-expired-p (object)
469   "Whether a cache of OBJECT is expired."
470   ;; FIXME: Use method instead of `(aref (cdr object) 0)'.
471   (let ((timestamp (aref (cdr object) 0)))
472     (unless (or (null mixi-cache-expires)
473                  (null timestamp))
474       (mixi-time-less-p
475        (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
476        (current-time)))))
477
478 (defun mixi-make-cache (key value table)
479   "Make a cache object and return it."
480   (let ((cache (gethash key table)))
481     (if (and cache (not (mixi-cache-expired-p cache)))
482         cache
483       (puthash key value table))))
484
485 ;; Object.
486 (defconst mixi-object-prefix "mixi-")
487
488 (defmacro mixi-object-class (object)
489   `(car-safe ,object))
490
491 (defmacro mixi-object-p (object)
492   `(eq (string-match (concat "^" mixi-object-prefix)
493                      (symbol-name (mixi-object-class ,object))) 0))
494
495 (defun mixi-object-name (object)
496   "Return the name of OBJECT."
497   (unless (mixi-object-p object)
498     (signal 'wrong-type-argument (list 'mixi-object-p object)))
499   (let ((class (mixi-object-class object)))
500     (substring (symbol-name class) (length mixi-object-prefix))))
501
502 (defun mixi-object-id (object)
503   "Return the id of OBJECT."
504   (unless (mixi-object-p object)
505     (signal 'wrong-type-argument (list 'mixi-object-p object)))
506   (let ((func (intern (concat mixi-object-prefix
507                               (mixi-object-name object) "-id"))))
508     (funcall func object)))
509
510 ;; Friend object.
511 (defvar mixi-friend-cache (make-hash-table :test 'equal))
512 (defun mixi-make-friend (id &optional nick)
513   "Return a friend object."
514   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick nil nil nil nil
515                                                  nil nil nil nil nil nil nil))
516                    mixi-friend-cache))
517
518 (defun mixi-make-me ()
519   (unless mixi-me
520     (with-mixi-retrieve "/home.pl"
521       (if (string-match mixi-my-id-regexp buffer)
522           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
523         (signal 'error (list 'who-am-i)))))
524   mixi-me)
525
526 (defmacro mixi-friend-p (friend)
527   `(eq (mixi-object-class ,friend) 'mixi-friend))
528
529 (defmacro mixi-friend-page (friend)
530   `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
531
532 (defconst mixi-friend-nick-regexp
533   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
534 (defconst mixi-friend-name-sex-regexp
535   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>\n+<td WIDTH=345>\\([^<]+\\) (\\([Ã˽÷]\\)À­)</td>")
536 (defconst mixi-friend-address-regexp
537   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
538 (defconst mixi-friend-age-regexp
539   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
540 (defconst mixi-friend-birthday-regexp
541   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
542 (defconst mixi-friend-blood-type-regexp
543   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
544 (defconst mixi-friend-birthplace-regexp
545   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
546 (defconst mixi-friend-hobby-regexp
547   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+\\)</td></tr>")
548 (defconst mixi-friend-job-regexp
549   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
550 (defconst mixi-friend-organization-regexp
551   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
552 (defconst mixi-friend-profile-regexp
553   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
554
555 (defun mixi-friend-realize (friend)
556   "Realize a FRIEND."
557   ;; FIXME: Check a expiration of cache?
558   (unless (mixi-friend-realize-p friend)
559     (let (buf)
560       (with-mixi-retrieve (mixi-friend-page friend)
561         (setq buf buffer))
562       (if (string-match mixi-friend-nick-regexp buf)
563           (mixi-friend-set-nick friend (match-string 1 buf))
564         (signal 'error (list 'cannot-find-nick friend)))
565       ;; For getting my profile.
566       (unless (string-match mixi-friend-name-sex-regexp buf)
567         (with-mixi-retrieve "/show_profile.pl"
568           (setq buf buffer)))
569       (if (string-match mixi-friend-name-sex-regexp buf)
570           (progn
571             (mixi-friend-set-name friend (match-string 2 buf))
572             (mixi-friend-set-sex friend
573                                  (if (string= (match-string 3 buf) "ÃË")
574                                      'male 'female)))
575         (signal 'error (list 'cannot-find-name-or-sex friend)))
576       (when (string-match mixi-friend-address-regexp buf)
577         (mixi-friend-set-address friend (match-string 1 buf)))
578       (when (string-match mixi-friend-age-regexp buf)
579         (mixi-friend-set-age
580          friend (string-to-number (match-string 2 buf))))
581       (when (string-match mixi-friend-birthday-regexp buf)
582         (mixi-friend-set-birthday
583          friend (list (string-to-number (match-string 1 buf))
584                       (string-to-number (match-string 2 buf)))))
585       (when (string-match mixi-friend-blood-type-regexp buf)
586         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
587       (when (string-match mixi-friend-birthplace-regexp buf)
588         (mixi-friend-set-birthplace friend (match-string 1 buf)))
589       (when (string-match mixi-friend-hobby-regexp buf)
590         (mixi-friend-set-hobby
591          friend (split-string (match-string 2 buf) ", ")))
592       (when (string-match mixi-friend-job-regexp buf)
593         (mixi-friend-set-job friend (match-string 2 buf)))
594       (when (string-match mixi-friend-organization-regexp buf)
595         (mixi-friend-set-organization friend (match-string 2 buf)))
596       (when (string-match mixi-friend-profile-regexp buf)
597         (mixi-friend-set-profile
598          friend (mixi-remove-markup (match-string 1 buf)))))
599     (mixi-friend-touch friend)))
600
601 (defun mixi-friend-realize-p (friend)
602   "Return the timestamp of FRIEND."
603   (unless (mixi-friend-p friend)
604     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
605   (aref (cdr friend) 0))
606
607 (defun mixi-friend-id (friend)
608   "Return the id of FRIEND."
609   (unless (mixi-friend-p friend)
610     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
611   (aref (cdr friend) 1))
612
613 (defun mixi-friend-nick (friend)
614   "Return the nick of FRIEND."
615   (unless (mixi-friend-p friend)
616     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
617   (unless (aref (cdr friend) 2)
618     (mixi-friend-realize friend))
619   (aref (cdr friend) 2))
620
621 (defun mixi-friend-name (friend)
622   "Return the name of FRIEND."
623   (unless (mixi-friend-p friend)
624     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
625   (mixi-friend-realize friend)
626   (aref (cdr friend) 3))
627
628 (defun mixi-friend-sex (friend)
629   "Return the sex of FRIEND."
630   (unless (mixi-friend-p friend)
631     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
632   (mixi-friend-realize friend)
633   (aref (cdr friend) 4))
634
635 (defun mixi-friend-address (friend)
636   "Return the address of FRIEND."
637   (unless (mixi-friend-p friend)
638     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
639   (mixi-friend-realize friend)
640   (aref (cdr friend) 5))
641
642 (defun mixi-friend-age (friend)
643   "Return the age of FRIEND."
644   (unless (mixi-friend-p friend)
645     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
646   (mixi-friend-realize friend)
647   (aref (cdr friend) 6))
648
649 (defun mixi-friend-birthday (friend)
650   "Return the birthday of FRIEND."
651   (unless (mixi-friend-p friend)
652     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
653   (mixi-friend-realize friend)
654   (aref (cdr friend) 7))
655
656 (defun mixi-friend-blood-type (friend)
657   "Return the blood type of FRIEND."
658   (unless (mixi-friend-p friend)
659     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
660   (mixi-friend-realize friend)
661   (aref (cdr friend) 8))
662
663 (defun mixi-friend-birthplace (friend)
664   "Return the birthplace of FRIEND."
665   (unless (mixi-friend-p friend)
666     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
667   (mixi-friend-realize friend)
668   (aref (cdr friend) 9))
669
670 (defun mixi-friend-hobby (friend)
671   "Return the hobby of FRIEND."
672   (unless (mixi-friend-p friend)
673     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
674   (mixi-friend-realize friend)
675   (aref (cdr friend) 10))
676
677 (defun mixi-friend-job (friend)
678   "Return the job of FRIEND."
679   (unless (mixi-friend-p friend)
680     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
681   (mixi-friend-realize friend)
682   (aref (cdr friend) 11))
683
684 (defun mixi-friend-organization (friend)
685   "Return the organization of FRIEND."
686   (unless (mixi-friend-p friend)
687     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
688   (mixi-friend-realize friend)
689   (aref (cdr friend) 12))
690
691 (defun mixi-friend-profile (friend)
692   "Return the pforile of FRIEND."
693   (unless (mixi-friend-p friend)
694     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
695   (mixi-friend-realize friend)
696   (aref (cdr friend) 13))
697
698 (defun mixi-friend-touch (friend)
699   "Set the timestamp of FRIEND."
700   (unless (mixi-friend-p friend)
701     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
702   (aset (cdr friend) 0 (current-time)))
703
704 (defun mixi-friend-set-nick (friend nick)
705   "Set the nick of FRIEND."
706   (unless (mixi-friend-p friend)
707     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
708   (aset (cdr friend) 2 nick))
709
710 (defun mixi-friend-set-name (friend name)
711   "Set the name of FRIEND."
712   (unless (mixi-friend-p friend)
713     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
714   (aset (cdr friend) 3 name))
715
716 (defun mixi-friend-set-sex (friend sex)
717   "Set the sex of FRIEND."
718   (unless (mixi-friend-p friend)
719     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
720   (aset (cdr friend) 4 sex))
721
722 (defun mixi-friend-set-address (friend address)
723   "Set the address of FRIEND."
724   (unless (mixi-friend-p friend)
725     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
726   (aset (cdr friend) 5 address))
727
728 (defun mixi-friend-set-age (friend age)
729   "Set the age of FRIEND."
730   (unless (mixi-friend-p friend)
731     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
732   (aset (cdr friend) 6 age))
733
734 (defun mixi-friend-set-birthday (friend birthday)
735   "Set the birthday of FRIEND."
736   (unless (mixi-friend-p friend)
737     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
738   (aset (cdr friend) 7 birthday))
739
740 (defun mixi-friend-set-blood-type (friend blood-type)
741   "Set the blood type of FRIEND."
742   (unless (mixi-friend-p friend)
743     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
744   (aset (cdr friend) 8 blood-type))
745
746 (defun mixi-friend-set-birthplace (friend birthplace)
747   "Set the birthplace of FRIEND."
748   (unless (mixi-friend-p friend)
749     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
750   (aset (cdr friend) 9 birthplace))
751
752 (defun mixi-friend-set-hobby (friend hobby)
753   "Set the hobby of FRIEND."
754   (unless (mixi-friend-p friend)
755     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
756   (aset (cdr friend) 10 hobby))
757
758 (defun mixi-friend-set-job (friend job)
759   "Set the job of FRIEND."
760   (unless (mixi-friend-p friend)
761     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
762   (aset (cdr friend) 11 job))
763
764 (defun mixi-friend-set-organization (friend organization)
765   "Set the organization of FRIEND."
766   (unless (mixi-friend-p friend)
767     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
768   (aset (cdr friend) 12 organization))
769
770 (defun mixi-friend-set-profile (friend profile)
771   "Set the profile of FRIEND."
772   (unless (mixi-friend-p friend)
773     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
774   (aset (cdr friend) 13 profile))
775
776 (defmacro mixi-friend-list-page (&optional friend)
777   `(concat "/list_friend.pl?page=%d"
778            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
779
780 (defconst mixi-friend-list-id-regexp
781   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
782 (defconst mixi-friend-list-nick-regexp
783   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
784
785 (defun mixi-get-friends (&rest args)
786   "Get friends of FRIEND."
787   (when (> (length args) 2)
788     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
789   (let ((friend (nth 0 args))
790         (max-numbers (nth 1 args)))
791     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
792       (setq friend (nth 1 args))
793       (setq max-numbers (nth 0 args)))
794     (unless (or (null friend) (mixi-friend-p friend))
795       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
796     (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
797                                        max-numbers
798                                        mixi-friend-list-id-regexp))
799           (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
800                                          max-numbers
801                                          mixi-friend-list-nick-regexp)))
802       (let ((index 0)
803             ret)
804         (while (< index (length ids))
805           (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
806                                             (nth 0 (nth index nicks))) ret))
807           (incf index))
808         (reverse ret)))))
809
810 ;; Favorite.
811 (defmacro mixi-favorite-list-page ()
812   `(concat "/list_bookmark.pl?page=%d"))
813
814 (defconst mixi-favorite-list-id-regexp
815   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
816 (defconst mixi-favorite-list-nick-regexp
817   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
818 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
819
820 (defun mixi-get-favorites (&optional max-numbers)
821   "Get favorites."
822   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
823                                      max-numbers
824                                      mixi-favorite-list-id-regexp))
825         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
826                                        max-numbers
827                                        mixi-favorite-list-nick-regexp)))
828     (let ((index 0)
829           ret)
830       (while (< index (length ids))
831         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
832                                           (nth 0 (nth index nicks))) ret))
833         (incf index))
834       (reverse ret))))
835
836 ;; Log object.
837 (defun mixi-make-log (friend time)
838   "Return a log object."
839   (cons 'mixi-log (vector friend time)))
840
841 (defmacro mixi-log-p (log)
842   `(eq (mixi-object-class ,log) 'mixi-log))
843
844 (defun mixi-log-friend (log)
845   "Return the friend of LOG."
846   (unless (mixi-log-p log)
847     (signal 'wrong-type-argument (list 'mixi-log-p log)))
848   (aref (cdr log) 0))
849
850 (defun mixi-log-time (log)
851   "Return the time of LOG."
852   (unless (mixi-log-p log)
853     (signal 'wrong-type-argument (list 'mixi-log-p log)))
854   (aref (cdr log) 1))
855
856 (defmacro mixi-log-list-page ()
857   `(concat "/show_log.pl"))
858
859 (defconst mixi-log-list-regexp
860   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a><br>")
861
862 (defun mixi-get-logs (&optional max-numbers)
863   "Get logs."
864   (let ((items (mixi-get-matched-items (mixi-log-list-page)
865                                        max-numbers
866                                        mixi-log-list-regexp)))
867     (mapcar (lambda (item)
868               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
869                              (encode-time 0
870                                           (string-to-number (nth 4 item))
871                                           (string-to-number (nth 3 item))
872                                           (string-to-number (nth 2 item))
873                                           (string-to-number (nth 1 item))
874                                           (string-to-number (nth 0 item)))))
875             items)))
876
877 ;; Diary object.
878 (defvar mixi-diary-cache (make-hash-table :test 'equal))
879 (defun mixi-make-diary (owner id)
880   "Return a diary object."
881   (let ((owner (or owner (mixi-make-me))))
882     (mixi-make-cache (list (mixi-friend-id owner) id)
883                      (cons 'mixi-diary (vector nil owner id nil nil nil))
884                      mixi-diary-cache)))
885
886 (defmacro mixi-diary-p (diary)
887   `(eq (mixi-object-class ,diary) 'mixi-diary))
888
889 (defmacro mixi-diary-page (diary)
890   `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
891            "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
892
893 ;; FIXME: Remove `¤µ¤ó'.
894 (defconst mixi-diary-owner-nick-regexp
895   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
896 (defconst mixi-diary-time-regexp
897   "<td ALIGN=center ROWSPAN=2 NOWRAP WIDTH=95 bgcolor=#FFD8B0>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
898 (defconst mixi-diary-title-regexp
899   "<td BGCOLOR=#FFF4E0 WIDTH=430>&nbsp;\\([^<]+\\)</td></tr>")
900 (defconst mixi-diary-content-regexp
901   "<td CLASS=h12>\\(.+\\)</td></tr>")
902
903 (defun mixi-diary-realize (diary)
904   "Realize a DIARY."
905   ;; FIXME: Check a expiration of cache?
906   (unless (mixi-diary-realize-p diary)
907     (with-mixi-retrieve (mixi-diary-page diary)
908       (if (string-match mixi-diary-owner-nick-regexp buffer)
909           (mixi-friend-set-nick (mixi-diary-owner diary)
910                                 (match-string 1 buffer))
911         (signal 'error (list 'cannot-find-owner-nick diary)))
912       (if (string-match mixi-diary-time-regexp buffer)
913           (mixi-diary-set-time
914            diary (encode-time 0 (string-to-number (match-string 5 buffer))
915                               (string-to-number (match-string 4 buffer))
916                               (string-to-number (match-string 3 buffer))
917                               (string-to-number (match-string 2 buffer))
918                               (string-to-number (match-string 1 buffer))))
919         (signal 'error (list 'cannot-find-time diary)))
920       (if (string-match mixi-diary-title-regexp buffer)
921           (mixi-diary-set-title diary (match-string 1 buffer))
922         (signal 'error (list 'cannot-find-title diary)))
923       (if (string-match mixi-diary-content-regexp buffer)
924           (mixi-diary-set-content diary (mixi-remove-markup
925                                          (match-string 1 buffer)))
926         (signal 'error (list 'cannot-find-content diary))))
927     (mixi-diary-touch diary)))
928
929 (defun mixi-diary-realize-p (diary)
930   "Return the timestamp of DIARY."
931   (unless (mixi-diary-p diary)
932     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
933   (aref (cdr diary) 0))
934
935 (defun mixi-diary-owner (diary)
936   "Return the owner of DIARY."
937   (unless (mixi-diary-p diary)
938     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
939   (aref (cdr diary) 1))
940
941 (defun mixi-diary-id (diary)
942   "Return the id of DIARY."
943   (unless (mixi-diary-p diary)
944     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
945   (aref (cdr diary) 2))
946
947 (defun mixi-diary-time (diary)
948   "Return the time of DIARY."
949   (unless (mixi-diary-p diary)
950     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
951   (mixi-diary-realize diary)
952   (aref (cdr diary) 3))
953
954 (defun mixi-diary-title (diary)
955   "Return the title of DIARY."
956   (unless (mixi-diary-p diary)
957     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
958   (mixi-diary-realize diary)
959   (aref (cdr diary) 4))
960
961 (defun mixi-diary-content (diary)
962   "Return the content of DIARY."
963   (unless (mixi-diary-p diary)
964     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
965   (mixi-diary-realize diary)
966   (aref (cdr diary) 5))
967
968 (defun mixi-diary-touch (diary)
969   "Set the timestamp of DIARY."
970   (unless (mixi-diary-p diary)
971     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
972   (aset (cdr diary) 0 (current-time)))
973
974 (defun mixi-diary-set-time (diary time)
975   "Set the time of DIARY."
976   (unless (mixi-diary-p diary)
977     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
978   (aset (cdr diary) 3 time))
979
980 (defun mixi-diary-set-title (diary title)
981   "Set the title of DIARY."
982   (unless (mixi-diary-p diary)
983     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
984   (aset (cdr diary) 4 title))
985
986 (defun mixi-diary-set-content (diary content)
987   "Set the content of DIARY."
988   (unless (mixi-diary-p diary)
989     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
990   (aset (cdr diary) 5 content))
991
992 (defmacro mixi-diary-list-page (&optional friend)
993   `(concat "/list_diary.pl?page=%d"
994            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
995
996 (defconst mixi-diary-list-regexp
997   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
998
999 (defun mixi-get-diaries (&rest args)
1000   "Get diaries of FRIEND."
1001   (when (> (length args) 2)
1002     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
1003   (let ((friend (nth 0 args))
1004         (max-numbers (nth 1 args)))
1005     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
1006       (setq friend (nth 1 args))
1007       (setq max-numbers (nth 0 args)))
1008     (unless (or (null friend) (mixi-friend-p friend))
1009       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1010     (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
1011                                          max-numbers
1012                                          mixi-diary-list-regexp)))
1013       (mapcar (lambda (item)
1014                 (mixi-make-diary friend (nth 0 item)))
1015               items))))
1016
1017 (defmacro mixi-new-diary-list-page ()
1018   `(concat "/new_friend_diary.pl?page=%d"))
1019
1020 (defconst mixi-new-diary-list-regexp
1021   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
1022
1023 (defun mixi-get-new-diaries (&optional max-numbers)
1024   "Get new diaries."
1025   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
1026                                        max-numbers
1027                                        mixi-new-diary-list-regexp)))
1028     (mapcar (lambda (item)
1029               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
1030             items)))
1031
1032 ;; Community object.
1033 (defvar mixi-community-cache (make-hash-table :test 'equal))
1034 (defun mixi-make-community (id &optional name)
1035   "Return a community object."
1036   (mixi-make-cache id (cons 'mixi-community (vector nil id name nil nil nil
1037                                                     nil nil nil nil))
1038                    mixi-community-cache))
1039
1040 (defmacro mixi-community-p (community)
1041   `(eq (mixi-object-class ,community) 'mixi-community))
1042
1043 (defmacro mixi-community-page (community)
1044   `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
1045
1046 (defconst mixi-community-nodata-regexp
1047   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1048 (defconst mixi-community-name-regexp
1049   "<td WIDTH=345>\\(.*\\)</td></tr>")
1050 (defconst mixi-community-birthday-regexp
1051   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>³«ÀßÆü</font></td>\n<td>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1052 ;; FIXME: Care when the owner has seceded.
1053 (defconst mixi-community-owner-regexp
1054   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>´ÉÍý¿Í</font></td>\n<td>\n\n<a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\n\\(.+\\)</a>")
1055 (defconst mixi-community-category-regexp
1056   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\n<td>\\([^<]+\\)</td>")
1057 (defconst mixi-community-members-regexp
1058   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\n<td>\\([0-9]+\\)¿Í</td></tr>")
1059 (defconst mixi-community-open-level-regexp
1060   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>
1061 <td>\\(.+\\)</td></tr>")
1062 (defconst mixi-community-authority-regexp
1063   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\n<td>\\(.+\\)</td></tr>")
1064 (defconst mixi-community-description-regexp
1065   "<td CLASS=h120>\\(.+\\)</td>")
1066
1067 (defun mixi-community-realize (community)
1068   "Realize a COMMUNITY."
1069   ;; FIXME: Check a expiration of cache?
1070   (unless (mixi-community-realize-p community)
1071     (with-mixi-retrieve (mixi-community-page community)
1072       (if (string-match mixi-community-nodata-regexp buffer)
1073           ;; FIXME: Set all members?
1074           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1075         (if (string-match mixi-community-name-regexp buffer)
1076             (mixi-community-set-name community (match-string 1 buffer))
1077           (signal 'error (list 'cannot-find-name community)))
1078         (if (string-match mixi-community-birthday-regexp buffer)
1079             (mixi-community-set-birthday
1080              community
1081              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
1082                           (string-to-number (match-string 2 buffer))
1083                           (string-to-number (match-string 1 buffer))))
1084           (signal 'error (list 'cannot-find-birthday community)))
1085         (if (string-match mixi-community-owner-regexp buffer)
1086             (if (string= (match-string 1 buffer) "home.pl")
1087                 (mixi-community-set-owner community (mixi-make-me))
1088               (mixi-community-set-owner
1089                community (mixi-make-friend (match-string 2 buffer)
1090                                            (match-string 3 buffer))))
1091           (signal 'error (list 'cannot-find-owner community)))
1092         (if (string-match mixi-community-category-regexp buffer)
1093             (mixi-community-set-category community (match-string 1 buffer))
1094           (signal 'error (list 'cannot-find-category community)))
1095         (if (string-match mixi-community-members-regexp buffer)
1096             (mixi-community-set-members
1097              community (string-to-number (match-string 1 buffer)))
1098           (signal 'error (list 'cannot-find-members community)))
1099         (if (string-match mixi-community-open-level-regexp buffer)
1100             (mixi-community-set-open-level community (match-string 1 buffer))
1101           (signal 'error (list 'cannot-find-open-level community)))
1102         (if (string-match mixi-community-authority-regexp buffer)
1103             (mixi-community-set-authority community (match-string 1 buffer))
1104           (signal 'error (list 'cannot-find-authority community)))
1105         (if (string-match mixi-community-description-regexp buffer)
1106             (mixi-community-set-description
1107              community (mixi-remove-markup (match-string 1 buffer)))
1108           (signal 'error (list 'cannot-find-description community)))))
1109     (mixi-community-touch community)))
1110
1111 (defun mixi-community-realize-p (community)
1112   "Return the timestamp of COMMUNITY."
1113   (unless (mixi-community-p community)
1114     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1115   (aref (cdr community) 0))
1116
1117 (defun mixi-community-id (community)
1118   "Return the id of COMMUNITY."
1119   (unless (mixi-community-p community)
1120     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1121   (aref (cdr community) 1))
1122
1123 (defun mixi-community-name (community)
1124   "Return the name of COMMUNITY."
1125   (unless (mixi-community-p community)
1126     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1127   (unless (aref (cdr community) 2)
1128     (mixi-community-realize community))
1129   (aref (cdr community) 2))
1130
1131 (defun mixi-community-birthday (community)
1132   "Return the birthday of COMMUNITY."
1133   (unless (mixi-community-p community)
1134     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1135   (mixi-community-realize community)
1136   (aref (cdr community) 3))
1137
1138 (defun mixi-community-owner (community)
1139   "Return the owner of COMMUNITY."
1140   (unless (mixi-community-p community)
1141     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1142   (mixi-community-realize community)
1143   (aref (cdr community) 4))
1144
1145 (defun mixi-community-category (community)
1146   "Return the category of COMMUNITY."
1147   (unless (mixi-community-p community)
1148     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1149   (mixi-community-realize community)
1150   (aref (cdr community) 5))
1151
1152 (defun mixi-community-members (community)
1153   "Return the members of COMMUNITY."
1154   (unless (mixi-community-p community)
1155     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1156   (mixi-community-realize community)
1157   (aref (cdr community) 6))
1158
1159 (defun mixi-community-open-level (community)
1160   "Return the open-level of COMMUNITY."
1161   (unless (mixi-community-p community)
1162     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1163   (mixi-community-realize community)
1164   (aref (cdr community) 7))
1165
1166 (defun mixi-community-authority (community)
1167   "Return the authority of COMMUNITY."
1168   (unless (mixi-community-p community)
1169     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1170   (mixi-community-realize community)
1171   (aref (cdr community) 8))
1172
1173 (defun mixi-community-description (community)
1174   "Return the description of COMMUNITY."
1175   (unless (mixi-community-p community)
1176     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1177   (mixi-community-realize community)
1178   (aref (cdr community) 9))
1179
1180 (defun mixi-community-touch (community)
1181   "Set the timestamp of COMMUNITY."
1182   (unless (mixi-community-p community)
1183     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1184   (aset (cdr community) 0 (current-time)))
1185
1186 (defun mixi-community-set-name (community name)
1187   "Set the name of COMMUNITY."
1188   (unless (mixi-community-p community)
1189     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1190   (aset (cdr community) 2 name))
1191
1192 (defun mixi-community-set-birthday (community birthday)
1193   "Set the birthday of COMMUNITY."
1194   (unless (mixi-community-p community)
1195     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1196   (aset (cdr community) 3 birthday))
1197
1198 (defun mixi-community-set-owner (community owner)
1199   "Set the owner of COMMUNITY."
1200   (unless (mixi-community-p community)
1201     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1202   (unless (mixi-friend-p owner)
1203     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1204   (aset (cdr community) 4 owner))
1205
1206 (defun mixi-community-set-category (community category)
1207   "Set the category of COMMUNITY."
1208   (unless (mixi-community-p community)
1209     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1210   (aset (cdr community) 5 category))
1211
1212 (defun mixi-community-set-members (community members)
1213   "Set the name of COMMUNITY."
1214   (unless (mixi-community-p community)
1215     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1216   (aset (cdr community) 6 members))
1217
1218 (defun mixi-community-set-open-level (community open-level)
1219   "Set the name of COMMUNITY."
1220   (unless (mixi-community-p community)
1221     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1222   (aset (cdr community) 7 open-level))
1223
1224 (defun mixi-community-set-authority (community authority)
1225   "Set the name of COMMUNITY."
1226   (unless (mixi-community-p community)
1227     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1228   (aset (cdr community) 8 authority))
1229
1230 (defun mixi-community-set-description (community description)
1231   "Set the name of COMMUNITY."
1232   (unless (mixi-community-p community)
1233     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1234   (aset (cdr community) 9 description))
1235
1236 (defmacro mixi-community-list-page (&optional friend)
1237   `(concat "/list_community.pl?page=%d"
1238            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1239
1240 (defconst mixi-community-list-id-regexp
1241   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1242 (defconst mixi-community-list-name-regexp
1243   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1244
1245 (defun mixi-get-communities (&rest args)
1246   "Get communities of FRIEND."
1247   (when (> (length args) 2)
1248     (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
1249   (let ((friend (nth 0 args))
1250         (max-numbers (nth 1 args)))
1251     (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
1252       (setq friend (nth 1 args))
1253       (setq max-numbers (nth 0 args)))
1254     (unless (or (null friend) (mixi-friend-p friend))
1255       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1256     (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1257                                        max-numbers
1258                                        mixi-community-list-id-regexp))
1259           (names (mixi-get-matched-items (mixi-community-list-page friend)
1260                                          max-numbers
1261                                          mixi-community-list-name-regexp)))
1262       (let ((index 0)
1263             ret)
1264         (while (< index (length ids))
1265           (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1266                                                (nth 0 (nth index names))) ret))
1267           (incf index))
1268         (reverse ret)))))
1269
1270 ;; Topic object.
1271 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1272 (defun mixi-make-topic (community id)
1273   "Return a topic object."
1274   (mixi-make-cache (list (mixi-community-id community) id)
1275                    (cons 'mixi-topic (vector nil community id nil nil nil nil))
1276                    mixi-topic-cache))
1277
1278 (defmacro mixi-topic-p (topic)
1279   `(eq (mixi-object-class ,topic) 'mixi-topic))
1280
1281 (defmacro mixi-topic-page (topic)
1282   `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
1283            "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
1284
1285 (defconst mixi-topic-time-regexp
1286   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1287 (defconst mixi-topic-title-regexp
1288   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1289 ;; FIXME: Remove `¤µ¤ó'.
1290 (defconst mixi-topic-owner-regexp
1291   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)\\(¤µ¤ó\\)?</a>")
1292 (defconst mixi-topic-content-regexp
1293   "<td class=\"h120\"><table><tr>\\(.+\\)?</tr></table>\\(.+\\)</td>")
1294
1295 (defun mixi-topic-realize (topic)
1296   "Realize a TOPIC."
1297   ;; FIXME: Check a expiration of cache?
1298   (unless (mixi-topic-realize-p topic)
1299     (with-mixi-retrieve (mixi-topic-page topic)
1300       (if (string-match mixi-topic-time-regexp buffer)
1301           (mixi-topic-set-time
1302            topic (encode-time 0 (string-to-number (match-string 5 buffer))
1303                               (string-to-number (match-string 4 buffer))
1304                               (string-to-number (match-string 3 buffer))
1305                               (string-to-number (match-string 2 buffer))
1306                               (string-to-number (match-string 1 buffer))))
1307         (signal 'error (list 'cannot-find-time topic)))
1308       (if (string-match mixi-topic-title-regexp buffer)
1309           (mixi-topic-set-title topic (match-string 1 buffer))
1310         (signal 'error (list 'cannot-find-title topic)))
1311       (if (string-match mixi-topic-owner-regexp buffer)
1312           (mixi-topic-set-owner topic
1313                                 (mixi-make-friend (match-string 1 buffer)
1314                                                   (match-string 2 buffer)))
1315         (signal 'error (list 'cannot-find-owner topic)))
1316       (if (string-match mixi-topic-content-regexp buffer)
1317           (mixi-topic-set-content topic (mixi-remove-markup
1318                                          (match-string 2 buffer)))
1319         (signal 'error (list 'cannot-find-content topic))))
1320     (mixi-topic-touch topic)))
1321
1322 (defun mixi-topic-realize-p (topic)
1323   "Return the timestamp of TOPIC."
1324   (unless (mixi-topic-p topic)
1325     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1326   (aref (cdr topic) 0))
1327
1328 (defun mixi-topic-community (topic)
1329   "Return the community of TOPIC."
1330   (unless (mixi-topic-p topic)
1331     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1332   (aref (cdr topic) 1))
1333
1334 (defun mixi-topic-id (topic)
1335   "Return the id of TOPIC."
1336   (unless (mixi-topic-p topic)
1337     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1338   (aref (cdr topic) 2))
1339
1340 (defun mixi-topic-time (topic)
1341   "Return the time of TOPIC."
1342   (unless (mixi-topic-p topic)
1343     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1344   (mixi-topic-realize topic)
1345   (aref (cdr topic) 3))
1346
1347 (defun mixi-topic-title (topic)
1348   "Return the title of TOPIC."
1349   (unless (mixi-topic-p topic)
1350     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1351   (mixi-topic-realize topic)
1352   (aref (cdr topic) 4))
1353
1354 (defun mixi-topic-owner (topic)
1355   "Return the owner of TOPIC."
1356   (unless (mixi-topic-p topic)
1357     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1358   (mixi-topic-realize topic)
1359   (aref (cdr topic) 5))
1360
1361 (defun mixi-topic-content (topic)
1362   "Return the content of TOPIC."
1363   (unless (mixi-topic-p topic)
1364     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1365   (mixi-topic-realize topic)
1366   (aref (cdr topic) 6))
1367
1368 (defun mixi-topic-touch (topic)
1369   "Set the timestamp of TOPIC."
1370   (unless (mixi-topic-p topic)
1371     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1372   (aset (cdr topic) 0 (current-time)))
1373
1374 (defun mixi-topic-set-time (topic time)
1375   "Set the time of TOPIC."
1376   (unless (mixi-topic-p topic)
1377     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1378   (aset (cdr topic) 3 time))
1379
1380 (defun mixi-topic-set-title (topic title)
1381   "Set the title of TOPIC."
1382   (unless (mixi-topic-p topic)
1383     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1384   (aset (cdr topic) 4 title))
1385
1386 (defun mixi-topic-set-owner (topic owner)
1387   "Set the owner of TOPIC."
1388   (unless (mixi-topic-p topic)
1389     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1390   (unless (mixi-friend-p owner)
1391     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1392   (aset (cdr topic) 5 owner))
1393
1394 (defun mixi-topic-set-content (topic content)
1395   "Set the content of TOPIC."
1396   (unless (mixi-topic-p topic)
1397     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1398   (aset (cdr topic) 6 content))
1399
1400 (defmacro mixi-topic-list-page (community)
1401   `(concat "/list_bbs.pl?page=%d"
1402            "&id=" (mixi-community-id ,community)))
1403
1404 (defconst mixi-topic-list-regexp
1405   "<a href=view_bbs\\.pl\\?id=\\([0-9]+\\)")
1406
1407 (defun mixi-get-topics (community &optional max-numbers)
1408   "Get topics of COMMUNITY."
1409   (unless (mixi-community-p community)
1410     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1411   (let ((items (mixi-get-matched-items (mixi-topic-list-page community)
1412                                        max-numbers
1413                                        mixi-topic-list-regexp)))
1414     (mapcar (lambda (item)
1415               (mixi-make-topic community (nth 0 item)))
1416             items)))
1417
1418 (defmacro mixi-new-topic-list-page ()
1419   `(concat "/new_bbs.pl?page=%d"))
1420
1421 (defconst mixi-new-topic-list-regexp
1422   "<a href=\"view_bbs\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
1423
1424 (defun mixi-get-new-topics (&optional max-numbers)
1425   "Get new topics."
1426   (let ((items (mixi-get-matched-items (mixi-new-topic-list-page)
1427                                        max-numbers
1428                                        mixi-new-topic-list-regexp)))
1429     (mapcar (lambda (item)
1430               (mixi-make-topic (mixi-make-community (nth 1 item))
1431                                (nth 0 item)))
1432             items)))
1433
1434 ;; Comment object.
1435 (defun mixi-make-comment (parent owner time content)
1436   "Return a comment object."
1437   (cons 'mixi-comment (vector parent owner time content)))
1438
1439 (defmacro mixi-comment-p (comment)
1440   `(eq (mixi-object-class ,comment) 'mixi-comment))
1441
1442 (defun mixi-comment-parent (comment)
1443   "Return the parent of COMMENT."
1444   (unless (mixi-comment-p comment)
1445     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1446   (aref (cdr comment) 0))
1447
1448 (defun mixi-comment-owner (comment)
1449   "Return the owner of COMMENT."
1450   (unless (mixi-comment-p comment)
1451     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1452   (aref (cdr comment) 1))
1453
1454 (defun mixi-comment-time (comment)
1455   "Return the time of COMMENT."
1456   (unless (mixi-comment-p comment)
1457     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1458   (aref (cdr comment) 2))
1459
1460 (defun mixi-comment-content (comment)
1461   "Return the content of COMMENT."
1462   (unless (mixi-comment-p comment)
1463     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1464   (aref (cdr comment) 3))
1465
1466 (defun mixi-diary-comment-list-page (diary)
1467   (concat "/view_diary.pl?page=%d"
1468           "&id=" (mixi-diary-id diary)
1469           "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
1470
1471 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1472 (defconst mixi-diary-comment-list-regexp
1473 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
1474 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
1475 <input type=checkbox name=comment_id value=\".+\">
1476 \\|\\)
1477 </td>
1478 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
1479 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
1480 <tr>
1481 <td>
1482 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1483
1484 </td>
1485 </tr>
1486 </table>
1487 </td>
1488 </tr>
1489 <!-- ËÜʸ : start -->
1490 <tr>
1491 <td bgcolor=\"#ffffff\">
1492 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
1493 <tr>
1494 <td CLASS=h12>
1495 \\(.+\\)
1496 </td></tr></table>")
1497
1498 (defun mixi-topic-comment-list-page (topic)
1499   (concat "/view_bbs.pl?page=%d"
1500           "&id=" (mixi-topic-id topic)
1501           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
1502
1503 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1504 (defconst mixi-topic-comment-list-regexp
1505   "<tr valign=\"top\">
1506 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
1507 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1508 \\([0-9]+\\):\\([0-9]+\\)<br>
1509 \\(</td>\\)
1510 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
1511 <b>&nbsp;&nbsp;[0-9]+</b>:</font>&nbsp;
1512
1513 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1514
1515
1516
1517 </td>
1518 </tr>
1519 <tr>
1520 <td bgcolor=\"#ffffff\" align=\"center\">
1521 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
1522 <tr>
1523 <td class=\"h120\">
1524
1525 \\(.+\\)
1526 </td>
1527 </tr>
1528 </table>
1529 </td>
1530 </tr>")
1531
1532 (defun mixi-get-comments (parent &optional max-numbers)
1533   "Get comments of PARENT."
1534   (unless (mixi-object-p parent)
1535     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
1536   (let* ((name (mixi-object-name parent))
1537          (list-page (intern (concat mixi-object-prefix name
1538                                     "-comment-list-page")))
1539          (regexp (eval (intern (concat mixi-object-prefix name
1540                                        "-comment-list-regexp")))))
1541     (let ((items (mixi-get-matched-items
1542                   (funcall list-page parent) max-numbers regexp)))
1543       (mapcar (lambda (item)
1544                 (mixi-make-comment parent (mixi-make-friend
1545                                            (nth 6 item) (nth 7 item))
1546                                    (encode-time
1547                                     0
1548                                     (string-to-number (nth 4 item))
1549                                     (string-to-number (nth 3 item))
1550                                     (string-to-number (nth 2 item))
1551                                     (string-to-number (nth 1 item))
1552                                     (string-to-number (nth 0 item)))
1553                                    (mixi-remove-markup (nth 8 item))))
1554               items))))
1555
1556 (defmacro mixi-new-comment-list-page ()
1557   `(concat "/new_comment.pl?page=%d"))
1558
1559 (defconst mixi-new-comment-list-regexp
1560   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
1561
1562 (defun mixi-get-new-comments (&optional max-numbers)
1563   "Get new comments."
1564   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
1565                                        max-numbers
1566                                        mixi-new-comment-list-regexp)))
1567     (mapcar (lambda (item)
1568               (let ((diary (mixi-make-diary
1569                             (mixi-make-friend (nth 1 item))
1570                             (nth 0 item))))
1571                 (mixi-get-comments diary)))
1572             items)))
1573
1574 (provide 'mixi)
1575
1576 ;;; mixi.el ends here