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