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