* mixi.el (mixi-diary-list-regexp): Fix regexp.
[elisp/mixi.git] / mixi.el
1 ;; mixi.el --- API library for accessing to mixi
2
3 ;; Copyright (C) 2005,2006 OHASHI Akira
4
5 ;; Author: OHASHI Akira <bg66@koka-in.org>
6 ;; Keywords: hypermedia
7
8 ;; This file is *NOT* a part of Emacs.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program; if not, you can either send email to this
22 ;; program's maintainer or write to: The Free Software Foundation,
23 ;; Inc.; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24
25 ;;; Commentary:
26
27 ;; API for getting contents:
28 ;;
29 ;;  * mixi-get-friends
30 ;;  * mixi-get-favorites
31 ;;  * mixi-get-logs
32 ;;  * mixi-get-diaries
33 ;;  * mixi-get-new-diaries
34 ;;  * mixi-search-diaries
35 ;;  * mixi-get-communities
36 ;;  * mixi-search-communities
37 ;;  * mixi-get-bbses
38 ;;  * mixi-get-new-bbses
39 ;;  * mixi-search-bbses
40 ;;  * mixi-get-comments
41 ;;  * mixi-get-new-comments
42 ;;  * mixi-get-messages
43 ;;  * mixi-get-introductions
44 ;;  * mixi-get-news
45 ;;
46 ;; API for posting:
47 ;;
48 ;;  * mixi-post-diary
49 ;;  * mixi-post-topic
50 ;;  * mixi-post-comment
51 ;;  * mixi-post-message
52 ;; 
53 ;; Utilities:
54 ;;
55 ;;  * mixi-remove-markup
56
57 ;; Example:
58 ;;
59 ;; Display newest 3 diaries like a mail format.
60 ;;
61 ;; (let ((range 3)
62 ;;       (buffer (get-buffer-create "*temp*"))
63 ;;       (format "%Y/%m/%d %H:%M"))
64 ;;   (pop-to-buffer buffer)
65 ;;   (mapc (lambda (diary)
66 ;;        (let ((subject (mixi-diary-title diary))
67 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
68 ;;              (date (format-time-string format (mixi-diary-time diary)))
69 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
70 ;;          (insert "From: " from "\n"
71 ;;                  "Subject: " subject "\n"
72 ;;                  "Date: " date "\n\n"
73 ;;                  body "\n\n")))
74 ;;      (mixi-get-new-diaries range))
75 ;;   (set-buffer-modified-p nil)
76 ;;   (setq buffer-read-only t)
77 ;;   (goto-char (point-min)))
78 ;;
79 ;; Display newest 3 diaries including newest 3 comments like a mail format.
80 ;; Comments are displayed like a reply mail.
81 ;;
82 ;; (let ((range 3)
83 ;;       (buffer (get-buffer-create "*temp*"))
84 ;;       (format "%Y/%m/%d %H:%M"))
85 ;;   (pop-to-buffer buffer)
86 ;;   (mapc (lambda (diary)
87 ;;        (let ((subject (mixi-diary-title diary))
88 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
89 ;;              (date (format-time-string format (mixi-diary-time diary)))
90 ;;              (body (mixi-remove-markup (mixi-diary-content diary))))
91 ;;          (insert "From: " from "\n"
92 ;;                  "Subject: " subject "\n"
93 ;;                  "Date: " date "\n\n"
94 ;;                  body "\n\n")
95 ;;          (mapc (lambda (comment)
96 ;;                  (let ((from (mixi-friend-nick
97 ;;                               (mixi-comment-owner comment)))
98 ;;                        (subject (concat "Re: " subject))
99 ;;                        (date (format-time-string
100 ;;                               format (mixi-comment-time comment)))
101 ;;                        (body (mixi-remove-markup
102 ;;                               (mixi-comment-content comment))))
103 ;;                    (insert "From: " from "\n"
104 ;;                            "Subject: " subject "\n"
105 ;;                            "Date: " date "\n\n"
106 ;;                            body "\n\n")))
107 ;;                (mixi-get-comments diary range))))
108 ;;      (mixi-get-new-diaries range))
109 ;;   (set-buffer-modified-p nil)
110 ;;   (setq buffer-read-only t)
111 ;;   (goto-char (point-min)))
112
113 ;; Bug reports:
114 ;;
115 ;; If you have bug reports and/or suggestions for improvement, please
116 ;; send them via <URL:http://mixi.jp/view_community.pl?id=1596390>.
117
118 ;;; Code:
119
120 (eval-when-compile (require 'cl))
121
122 (defgroup mixi nil
123   "API library for accessing to mixi."
124   :group 'hypermedia)
125
126 (defcustom mixi-url "http://mixi.jp"
127   "*The URL of mixi."
128   :type 'string
129   :group 'mixi)
130
131 (defcustom mixi-directory (expand-file-name "~/.mixi")
132   "*Where to look for mixi files."
133   :type 'directory
134   :group 'mixi)
135
136 (defcustom mixi-coding-system 'euc-jp
137   "*Coding system for mixi."
138   :type 'coding-system
139   :group 'mixi)
140
141 (defcustom mixi-curl-program "curl"
142   "*The program name of `curl'."
143   :type 'file
144   :group 'mixi)
145
146 (defcustom mixi-curl-cookie-file (expand-file-name "cookies.txt"
147                                                    mixi-directory)
148   "*The location of cookie file created by `curl'."
149   :type 'file
150   :group 'mixi)
151
152 (defcustom mixi-backend
153   (or (condition-case nil
154           (progn
155             (require 'w3m)
156             'w3m)
157         (error))
158       (condition-case nil
159           (progn
160             (require 'url)
161             (if (fboundp 'url-retrieve-synchronously)
162                 'url))
163         (error))
164       (if (and (fboundp 'executable-find)
165                (executable-find mixi-curl-program))
166           'curl)
167       (error "Cannot set `mixi-backend'."))
168   "*The backend for accessing to mixi."
169   :type '(radio (const :tag "Use emacs-w3m" w3m)
170                 (const :tag "Use url.el" url)
171                 (const :tag "Use curl" curl)
172                 (symbol :tag "The other backend"))
173   :group 'mixi)
174
175 (defcustom mixi-default-email nil
176   "*Default E-mail address that is used to login automatically."
177   :type '(radio (string :tag "E-mail address")
178                 (const :tag "Asked when it is necessary" nil))
179   :group 'mixi)
180
181 (defcustom mixi-default-password nil
182   "*Default password that is used to login automatically."
183   :type '(radio (string :tag "Password")
184                 (const :tag "Asked when it is necessary" nil))
185   :group 'mixi)
186
187 (defcustom mixi-accept-adult-contents t
188   "*If non-nil, accept to access to the adult contents."
189   :type 'boolean
190   :group 'mixi)
191
192 (defcustom mixi-continuously-access-interval 4.0
193   "*Time interval between each mixi access.
194 Increase this value when unexpected error frequently occurs."
195   :type 'number
196   :group 'mixi)
197
198 (defcustom mixi-cache-expires nil
199   "*Seconds for expiration of a cached object."
200   :type '(radio (integer :tag "Expired seconds")
201                 (const :tag "Don't expire" nil)
202                 (const :tag "Don't cache" t))
203   :group 'mixi)
204
205 ;; FIXME: Not implemented.
206 (defcustom mixi-cache-use-file t
207   "*If non-nil, caches are saved to files."
208   :type 'boolean
209   :group 'mixi)
210
211 (defvar mixi-me nil)
212
213 ;; Utilities.
214 (defmacro mixi-message (&rest strings)
215   `(concat "[mixi] " ,@strings))
216
217 (put 'mixi-realization-error
218      'error-message (mixi-message "Cannot realize object"))
219 (put 'mixi-realization-error
220      'error-conditions '(mixi-realization-error error))
221
222 (put 'mixi-post-error
223      'error-message (mixi-message "Cannot post"))
224 (put 'mixi-post-error
225      'error-conditions '(mixi-post-error error))
226
227 (defmacro mixi-realization-error (type object)
228   `(let ((data (if (and (boundp 'buffer) debug-on-error)
229                    (list ,type ,object buffer)
230                  (list ,type ,object))))
231      (signal 'mixi-realization-error data)))
232
233 (defmacro mixi-post-error (type &optional object)
234   `(let ((data (when (and (boundp 'buffer) debug-on-error)
235                  (list buffer))))
236      (if ,object
237          (setq data (cons ,type (cons ,object data)))
238        (setq data (cons ,type data)))
239      (signal 'mixi-post-error data)))
240
241 (defconst mixi-message-adult-contents
242   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
243 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
244 (defconst mixi-message-continuously-accessing
245   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
246 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
247 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
248 (defconst mixi-warning-continuously-accessing
249   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
250 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
251 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
252
253 (defmacro mixi-retrieve (url &optional post-data)
254   `(funcall (intern (concat "mixi-" (symbol-name mixi-backend) "-retrieve"))
255             ,url ,post-data))
256
257 (defmacro mixi-post-form (url fields)
258   `(funcall (intern (concat "mixi-" (symbol-name mixi-backend) "-post-form"))
259             ,url ,fields))
260
261 (defun mixi-parse-buffer (url buffer &optional post-data)
262   (when (string-match mixi-message-adult-contents buffer)
263     (if mixi-accept-adult-contents
264         (setq buffer (mixi-retrieve url "submit=agree"))
265       (setq buffer (mixi-retrieve (concat url "?")))))
266   (when (string-match mixi-warning-continuously-accessing buffer)
267     (error (mixi-message "Access denied.  Please wait a while and increase "
268                          "the value of `mixi-continuously-access-interval'.")))
269   (if (not (string-match mixi-message-continuously-accessing buffer))
270       buffer
271     (message (mixi-message "Waiting for continuously accessing..."))
272     (sleep-for mixi-continuously-access-interval)
273     (mixi-retrieve url post-data)))
274
275 (defmacro mixi-expand-url (url)
276   `(if (string-match "^http" ,url)
277        ,url
278      (concat mixi-url ,url)))
279
280 ;; FIXME: Support file, checkbox and so on.
281 (defun mixi-make-form-data (fields)
282   "Make form data and return (CONTENT-TYPE . FORM-DATA)."
283   (let* ((boundary (apply 'format "--_%d_%d_%d" (current-time)))
284          (content-type (concat "multipart/form-data; boundary=" boundary))
285          (form-data
286           (mapconcat
287            (lambda (field)
288              (concat "--" boundary "\r\n"
289                      "Content-Disposition: form-data; name=\""
290                      (car field) "\"\r\n"
291                      "\r\n"
292                      (encode-coding-string (cdr field) mixi-coding-system)))
293            fields "\r\n")))
294     (cons content-type (concat form-data "\r\n--" boundary "--"))))
295
296 (defun mixi-url-retrieve (url &optional post-data extra-headers)
297   "Retrieve the URL and return gotten strings."
298   (let* ((url-request-method (if post-data "POST" "GET"))
299          (url-request-data post-data)
300          (url-request-extra-headers extra-headers)
301          (url (mixi-expand-url url))
302          (buffer (url-retrieve-synchronously url))
303          ret)
304     (unless (bufferp buffer)
305       (error (mixi-message "Cannot retrieve")))
306     (with-current-buffer buffer
307       (goto-char (point-min))
308       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
309         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
310       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
311         (error (mixi-message "Cannot retrieve")))
312       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
313       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
314       (kill-buffer buffer)
315       (mixi-parse-buffer url ret post-data))))
316
317 (defun mixi-url-post-form (url fields)
318   (let* ((form-data (mixi-make-form-data fields))
319          (extra-headers `(("Content-Type" . ,(car form-data)))))
320     (mixi-url-retrieve url (cdr form-data) extra-headers)))
321
322 (defun mixi-w3m-retrieve (url &optional post-data)
323   "Retrieve the URL and return gotten strings."
324   (let ((url (mixi-expand-url url)))
325     (with-temp-buffer
326       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
327           (error (mixi-message "Cannot retrieve"))
328         (w3m-decode-buffer url)
329         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
330           (mixi-parse-buffer url ret post-data))))))
331
332 (defun mixi-w3m-post-form (url fields)
333   (let ((form-data (mixi-make-form-data fields)))
334     (mixi-w3m-retrieve url form-data)))
335
336 (defun mixi-curl-retrieve (url &optional post-data)
337   "Retrieve the URL and return gotten strings."
338   (with-temp-buffer
339     (if (fboundp 'set-buffer-multibyte)
340         (set-buffer-multibyte nil))
341     (let ((orig-mode (default-file-modes))
342           (coding-system-for-read 'binary)
343           (coding-system-for-write 'binary)
344           process ret)
345       (unwind-protect
346           (progn
347             (set-default-file-modes 448)
348             (setq process
349                   (apply #'start-process "curl" (current-buffer)
350                          mixi-curl-program
351                          (append (if post-data '("-d" "@-"))
352                                  (list "-i" "-L" "-s"
353                                        "-b" mixi-curl-cookie-file
354                                        "-c" mixi-curl-cookie-file
355                                        (mixi-expand-url url)))))
356             (set-process-sentinel process #'ignore))
357         (set-default-file-modes orig-mode))
358       (when post-data
359         (process-send-string process (concat post-data "\n"))
360         (process-send-eof process))
361       (while (eq (process-status process) 'run)
362         (accept-process-output process 1))
363       (goto-char (point-min))
364       (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
365         (delete-region (point) (re-search-forward "\r?\n\r?\n")))
366       (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
367         (error (mixi-message "Cannot retrieve")))
368       (delete-region (point) (re-search-forward "\r?\n\r?\n"))
369       (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
370       (mixi-parse-buffer url ret post-data))))
371
372 (defconst mixi-my-id-regexp
373   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
374
375 (defun mixi-login (&optional email password)
376   "Login to mixi."
377   (when (and (eq mixi-backend 'w3m) (not w3m-use-cookies))
378     (error (mixi-message "Require to accept cookies.  Please set "
379                          "`w3m-use-cookies' to t.")))
380   (let ((email (or email mixi-default-email
381                    (read-from-minibuffer (mixi-message "Login Email: "))))
382         (password (or password mixi-default-password
383                       (read-passwd (mixi-message "Login Password: ")))))
384     (let ((buffer (mixi-retrieve "/login.pl"
385                                  (concat "email=" email
386                                          "&password=" password
387                                          "&next_url=/home.pl"
388                                          "&sticky=on"))))
389       (unless (string-match "url=/check\\.pl\\?n=" buffer)
390         (error (mixi-message "Cannot login")))
391       (setq buffer (mixi-retrieve "/check.pl?n=home.pl"))
392       (if (string-match mixi-my-id-regexp buffer)
393           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
394         (error (mixi-message "Cannot login"))))))
395
396 (defun mixi-logout ()
397   (mixi-retrieve "/logout.pl"))
398
399 (defmacro with-mixi-retrieve (url &rest body)
400   `(let (buffer)
401      (when ,url
402        (setq buffer (mixi-retrieve ,url))
403        (when (string-match "<form action=\"login\\.pl\" method=\"post\">"
404                            buffer)
405          (mixi-login)
406          (setq buffer (mixi-retrieve ,url))))
407      ,@body))
408 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
409 (put 'with-mixi-retrieve 'edebug-form-spec '(form body))
410
411 (defmacro with-mixi-post-form (url fields &rest body)
412   `(let (buffer)
413      (when ,url
414        (setq buffer (mixi-post-form ,url ,fields))
415        (when (string-match "<form action=\"login\\.pl\" method=\"post\">"
416                            buffer)
417          (mixi-login)
418          (setq buffer (mixi-post-form ,url ,fields))))
419      ,@body))
420 (put 'with-mixi-post-form 'lisp-indent-function 'defun)
421 (put 'with-mixi-post-form 'edebug-form-spec '(form body))
422
423 (defun mixi-get-matched-items (url regexp &optional range)
424   "Get matched items to REGEXP in URL."
425   (let ((page 1)
426         ids)
427     (catch 'end
428       (while (or (null range) (< (length ids) range))
429         (with-mixi-retrieve (format url page)
430           (let ((pos 0)
431                 found)
432             (while (and (string-match regexp buffer pos)
433                         (or (null range) (< (length ids) range)))
434               (let ((num 1)
435                     list)
436                 (while (match-string num buffer)
437                   (setq list (cons (match-string num buffer) list))
438                   (incf num))
439                 (when (not (member (reverse list) ids))
440                   (setq found t)
441                   (setq ids (cons (reverse list) ids)))
442                 (setq pos (match-end (1- num)))))
443             (when (not found)
444               (throw 'end ids))))
445         (incf page)))
446     (reverse ids)))
447
448 ;; stolen (and modified) from shimbun.el
449 (defun mixi-remove-markup (string)
450   "Remove markups from STRING."
451   (with-temp-buffer
452     (insert (or string ""))
453     (save-excursion
454       (goto-char (point-min))
455       (while (search-forward "<!--" nil t)
456         (delete-region (match-beginning 0)
457                        (or (search-forward "-->" nil t)
458                            (point-max))))
459       (goto-char (point-min))
460       (while (re-search-forward "<[^>]+>" nil t)
461         (replace-match "" t t))
462       (goto-char (point-min))
463       (while (re-search-forward "\r" nil t)
464         (replace-match "\n" t t)))
465     ;; FIXME: Decode entities.
466     (buffer-string)))
467
468 ;; stolen (and modified) from w3m.el
469 ;; FIXME: Hmm.
470 (defun mixi-url-encode-and-quote-percent-string (string)
471   (apply (function concat)
472          (mapcar
473           (lambda (char)
474             (cond
475              ((eq char ?\n)             ; newline
476               "%%0D%%0A")
477              ((string-match "[-a-zA-Z0-9_:/.]" (char-to-string char)) ; xxx?
478               (char-to-string char))    ; printable
479              ((char-equal char ?\x20)   ; space
480               "+")
481              (t
482               (format "%%%%%02x" char))))       ; escape
483           ;; Coerce a string into a list of chars.
484           (append (encode-coding-string (or string "") mixi-coding-system)
485                   nil))))
486
487 ;; Object.
488 (defconst mixi-object-prefix "mixi-")
489
490 (defmacro mixi-object-class (object)
491   `(car-safe ,object))
492
493 (defmacro mixi-object-p (object)
494   `(let ((class (mixi-object-class ,object)))
495      (when (symbolp class)
496        (eq (string-match (concat "^" mixi-object-prefix)
497                          (symbol-name class)) 0))))
498
499 (defun mixi-object-name (object)
500   "Return the name of OBJECT."
501   (unless (mixi-object-p object)
502     (signal 'wrong-type-argument (list 'mixi-object-p object)))
503   (let ((class (mixi-object-class object)))
504     (substring (symbol-name class) (length mixi-object-prefix))))
505
506 (defun mixi-read-object (exp)
507   "Read one Lisp expression as mixi object."
508   (if (mixi-object-p exp)
509       (let ((func (intern (concat mixi-object-prefix "make-"
510                                   (mixi-object-name exp))))
511             (args (mapcar (lambda (arg)
512                             (mixi-read-object arg))
513                           (cdr exp))))
514         (let ((object (apply func (cdr args))))
515           (when (car args)
516             (mixi-object-set-timestamp object (car args)))
517           object))
518     exp))
519
520 (defun mixi-object-timestamp (object)
521   "Return the timestamp of OJBECT."
522   (unless (mixi-object-p object)
523     (signal 'wrong-type-argument (list 'mixi-object-p object)))
524   (aref (cdr object) 0))
525 (defalias 'mixi-object-realized-p 'mixi-object-timestamp)
526
527 (defun mixi-object-owner (object)
528   "Return the owner of OBJECT."
529   (unless (mixi-object-p object)
530     (signal 'wrong-type-argument (list 'mixi-object-p object)))
531   (let ((func (intern (concat mixi-object-prefix
532                               (mixi-object-name object) "-owner"))))
533     (funcall func object)))
534
535 (defun mixi-object-id (object)
536   "Return the id of OBJECT."
537   (unless (mixi-object-p object)
538     (signal 'wrong-type-argument (list 'mixi-object-p object)))
539   (let ((func (intern (concat mixi-object-prefix
540                               (mixi-object-name object) "-id"))))
541     (funcall func object)))
542
543 (defun mixi-object-time (object)
544   "Return the time of OBJECT."
545   (unless (mixi-object-p object)
546     (signal 'wrong-type-argument (list 'mixi-object-p object)))
547   (let ((func (intern (concat mixi-object-prefix
548                               (mixi-object-name object) "-time"))))
549     (funcall func object)))
550
551 (defun mixi-object-title (object)
552   "Return the title of OBJECT."
553   (unless (mixi-object-p object)
554     (signal 'wrong-type-argument (list 'mixi-object-p object)))
555   (let ((func (intern (concat mixi-object-prefix
556                               (mixi-object-name object) "-title"))))
557     (funcall func object)))
558
559 (defun mixi-object-content (object)
560   "Return the content of OBJECT."
561   (unless (mixi-object-p object)
562     (signal 'wrong-type-argument (list 'mixi-object-p object)))
563   (let ((func (intern (concat mixi-object-prefix
564                               (mixi-object-name object) "-content"))))
565     (funcall func object)))
566
567 (defun mixi-object-set-timestamp (object timestamp)
568   "Set the timestamp of OBJECT."
569   (unless (mixi-object-p object)
570     (signal 'wrong-type-argument (list 'mixi-object-p object)))
571   (aset (cdr object) 0 timestamp))
572
573 (defmacro mixi-object-touch (object)
574   `(mixi-object-set-timestamp ,object (current-time)))
575
576 (defconst mixi-object-url-regexp
577   "/\\(show\\|view\\)_\\([a-z]+\\)\\.pl")
578
579 (defun mixi-make-object-from-url (url)
580   "Return a mixi object from URL."
581   (if (string-match mixi-object-url-regexp url)
582       (let ((name (match-string 2 url)))
583         (when (string= name "bbs")
584           (setq name "topic"))
585         (let ((func (intern (concat mixi-object-prefix "make-" name
586                                     "-from-url"))))
587           (funcall func url)))
588     (when (string-match "/home\\.pl" url)
589       (mixi-make-friend-from-url url))))
590
591 ;; Cache.
592 ;; stolen from time-date.el
593 (defun mixi-time-less-p (t1 t2)
594   "Say whether time value T1 is less than time value T2."
595   (unless (numberp (cdr t1))
596     (setq t1 (cons (car t1) (car (cdr t1)))))
597   (unless (numberp (cdr t2))
598     (setq t2 (cons (car t2) (car (cdr t2)))))
599   (or (< (car t1) (car t2))
600       (and (= (car t1) (car t2))
601            (< (cdr t1) (cdr t2)))))
602
603 (defun mixi-time-add (t1 t2)
604   "Add two time values.  One should represent a time difference."
605   (unless (numberp (cdr t1))
606     (setq t1 (cons (car t1) (car (cdr t1)))))
607   (unless (numberp (cdr t2))
608     (setq t2 (cons (car t2) (car (cdr t2)))))
609   (let ((low (+ (cdr t1) (cdr t2))))
610     (cons (+ (car t1) (car t2) (lsh low -16)) low)))
611
612 ;; stolen from time-date.el
613 (defun mixi-seconds-to-time (seconds)
614   "Convert SECONDS (a floating point number) to a time value."
615   (cons (floor seconds 65536)
616         (floor (mod seconds 65536))))
617
618 (defun mixi-cache-expired-p (object)
619   "Whether a cache of OBJECT is expired."
620   (let ((timestamp (mixi-object-timestamp object)))
621     (unless (or (null mixi-cache-expires)
622                  (null timestamp))
623       (if (numberp mixi-cache-expires)
624           (mixi-time-less-p
625            (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
626            (current-time))
627         t))))
628
629 (defun mixi-make-cache (key value table)
630   "Make a cache object and return it."
631   (let ((cache (gethash key table)))
632     (if (and cache (not (mixi-cache-expired-p cache)))
633         cache
634       (puthash key value table))))
635
636 (defconst mixi-cache-file-regexp "[a-z]+-cache$")
637 (defconst mixi-cache-regexp (concat mixi-object-prefix
638                                     mixi-cache-file-regexp))
639
640 (defun mixi-save-cache ()
641   (let ((cache-directory (expand-file-name "cache" mixi-directory)))
642     (unless (file-directory-p cache-directory)
643       (make-directory cache-directory t))
644     (let ((caches (apropos-internal mixi-cache-regexp 'boundp)))
645       (mapc (lambda (symbol)
646               (with-temp-file (expand-file-name
647                                (substring (symbol-name symbol)
648                                           (length mixi-object-prefix))
649                                cache-directory)
650                 (let ((coding-system-for-write mixi-coding-system)
651                       (cache (symbol-value symbol)))
652                   (insert "#s(hash-table size "
653                           (number-to-string (hash-table-count cache))
654                           " test equal data (\n")
655                   (maphash
656                    (lambda (key value)
657                      (let (print-level print-length)
658                        (insert (prin1-to-string key) " "
659                                (prin1-to-string value) "\n")))
660                    cache))
661                 (insert "))")))
662             caches))))
663
664 ;; stolen (and modified) from lsdb.el
665 (defun mixi-read-cache (&optional marker)
666   "Read one Lisp expression as text from MARKER, return as Lisp object."
667   (save-excursion
668     (goto-char marker)
669     (if (looking-at "^#s(")
670         (let ((end-marker
671                (progn
672                  (forward-char 2);skip "#s"
673                  (forward-sexp);move to the left paren
674                  (point-marker))))
675           (with-temp-buffer
676             (buffer-disable-undo)
677             (insert-buffer-substring (marker-buffer marker)
678                                      marker end-marker)
679             (goto-char (point-min))
680             (delete-char 2)
681             (let ((object (read (current-buffer)))
682                   data)
683               (if (eq 'hash-table (car object))
684                   (progn
685                     (setq data (plist-get (cdr object) 'data))
686                     (while data
687                       (pop data);throw it away
688                       (mixi-read-object (pop data))))
689                 object))))
690       (read marker))))
691
692 (defun mixi-load-cache ()
693   (let ((cache-directory (expand-file-name "cache" mixi-directory)))
694     (when (file-directory-p cache-directory)
695       ;; FIXME: Load friend and community first.
696       (let ((files (directory-files cache-directory t
697                                     mixi-cache-file-regexp)))
698         (mapc (lambda (file)
699                 (let ((buffer (find-file-noselect file)))
700                   (unwind-protect
701                       (save-excursion
702                         (set-buffer buffer)
703                         (goto-char (point-min))
704                         (re-search-forward "^#s(")
705                         (goto-char (match-beginning 0))
706                         (mixi-read-cache (point-marker)))
707                     (kill-buffer buffer))))
708               files)))))
709
710 ;; Friend object.
711 (defvar mixi-friend-cache (make-hash-table :test 'equal))
712 (defun mixi-make-friend (id &optional nick name sex address age birthday
713                             blood-type birthplace hobby job organization
714                             profile)
715   "Return a friend object."
716   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick name sex address
717                                                  age birthday blood-type
718                                                  birthplace hobby job
719                                                  organization profile))
720                    mixi-friend-cache))
721
722 (defun mixi-make-me ()
723   "Return a my object."
724   (unless mixi-me
725     (with-mixi-retrieve "/home.pl"
726       (if (string-match mixi-my-id-regexp buffer)
727           (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
728         (signal 'error (list 'who-am-i)))))
729   mixi-me)
730
731 (defconst mixi-friend-url-regexp
732   "/show_friend\\.pl\\?id=\\([0-9]+\\)")
733
734 (defun mixi-make-friend-from-url (url)
735   "Return a friend object from URL."
736   (if (string-match mixi-friend-url-regexp url)
737       (let ((id (match-string 1 url)))
738         (mixi-make-friend id))
739     (when (string-match "/home\\.pl" url)
740       (mixi-make-me))))
741
742 (defmacro mixi-friend-p (friend)
743   `(eq (mixi-object-class ,friend) 'mixi-friend))
744
745 (defmacro mixi-friend-page (friend)
746   `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
747
748 (defconst mixi-friend-nick-regexp
749   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\r?
750 \\(.*\\)¤µ¤ó([0-9]+)")
751 (defconst mixi-friend-name-regexp
752   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>
753
754 ?<td WIDTH=345>\\(.+?\\)\\(</td>\\|<img\\)")
755 (defconst mixi-friend-sex-regexp
756   "<td BGCOLOR=#F2DDB7\\( WIDTH=80 NOWRAP\\)?><font COLOR=#996600>À­\\(&nbsp;\\| \\)ÊÌ</font></td>
757
758 ?<td WIDTH=345>\\([Ã˽÷]\\)À­\\(</td>\\|<img\\)")
759 (defconst mixi-friend-address-regexp
760   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
761 (defconst mixi-friend-age-regexp
762   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
763 (defconst mixi-friend-birthday-regexp
764   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
765 (defconst mixi-friend-blood-type-regexp
766   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
767 (defconst mixi-friend-birthplace-regexp
768   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
769 (defconst mixi-friend-hobby-regexp
770   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+?\\)\\(</td>\\|<img\\)")
771 (defconst mixi-friend-job-regexp
772   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
773 (defconst mixi-friend-organization-regexp
774   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
775 (defconst mixi-friend-profile-regexp
776   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
777
778 (defun mixi-realize-friend (friend)
779   "Realize a FRIEND."
780   ;; FIXME: Check a expiration of cache?
781   (unless (mixi-object-realized-p friend)
782     (let (buf)
783       (with-mixi-retrieve (mixi-friend-page friend)
784         (setq buf buffer))
785       (if (string-match mixi-friend-nick-regexp buf)
786           (mixi-friend-set-nick friend (match-string 1 buf))
787         (mixi-realization-error 'cannot-find-nick friend))
788       ;; For getting my profile.
789       (unless (string-match mixi-friend-name-regexp buf)
790         (with-mixi-retrieve (concat "/show_profile.pl?id="
791                                     (mixi-friend-id friend))
792           (setq buf buffer)))
793       (if (string-match mixi-friend-name-regexp buf)
794             (mixi-friend-set-name friend (match-string 2 buf))
795         (mixi-realization-error 'cannot-find-name friend))
796       (if (string-match mixi-friend-sex-regexp buf)
797           (mixi-friend-set-sex friend
798                                (if (string= (match-string 3 buf) "ÃË")
799                                    'male 'female))
800         (mixi-realization-error 'cannot-find-sex friend))
801       (when (string-match mixi-friend-address-regexp buf)
802         (mixi-friend-set-address friend (match-string 1 buf)))
803       (when (string-match mixi-friend-age-regexp buf)
804         (mixi-friend-set-age
805          friend (string-to-number (match-string 2 buf))))
806       (when (string-match mixi-friend-birthday-regexp buf)
807         (mixi-friend-set-birthday
808          friend (list (string-to-number (match-string 1 buf))
809                       (string-to-number (match-string 2 buf)))))
810       (when (string-match mixi-friend-blood-type-regexp buf)
811         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
812       (when (string-match mixi-friend-birthplace-regexp buf)
813         (mixi-friend-set-birthplace friend (match-string 1 buf)))
814       (when (string-match mixi-friend-hobby-regexp buf)
815         (mixi-friend-set-hobby
816          friend (split-string (match-string 2 buf) ", ")))
817       (when (string-match mixi-friend-job-regexp buf)
818         (mixi-friend-set-job friend (match-string 2 buf)))
819       (when (string-match mixi-friend-organization-regexp buf)
820         (mixi-friend-set-organization friend (match-string 2 buf)))
821       (when (string-match mixi-friend-profile-regexp buf)
822         (mixi-friend-set-profile friend (match-string 1 buf))))
823     (mixi-object-touch friend)))
824
825 (defun mixi-friend-id (friend)
826   "Return the id of FRIEND."
827   (unless (mixi-friend-p friend)
828     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
829   (aref (cdr friend) 1))
830
831 (defun mixi-friend-nick (friend)
832   "Return the nick of FRIEND."
833   (unless (mixi-friend-p friend)
834     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
835   (unless (aref (cdr friend) 2)
836     (mixi-realize-friend friend))
837   (aref (cdr friend) 2))
838
839 (defun mixi-friend-name (friend)
840   "Return the name of FRIEND."
841   (unless (mixi-friend-p friend)
842     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
843   (mixi-realize-friend friend)
844   (aref (cdr friend) 3))
845
846 (defun mixi-friend-sex (friend)
847   "Return the sex of FRIEND."
848   (unless (mixi-friend-p friend)
849     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
850   (mixi-realize-friend friend)
851   (aref (cdr friend) 4))
852
853 (defun mixi-friend-address (friend)
854   "Return the address of FRIEND."
855   (unless (mixi-friend-p friend)
856     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
857   (mixi-realize-friend friend)
858   (aref (cdr friend) 5))
859
860 (defun mixi-friend-age (friend)
861   "Return the age of FRIEND."
862   (unless (mixi-friend-p friend)
863     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
864   (mixi-realize-friend friend)
865   (aref (cdr friend) 6))
866
867 (defun mixi-friend-birthday (friend)
868   "Return the birthday of FRIEND."
869   (unless (mixi-friend-p friend)
870     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
871   (mixi-realize-friend friend)
872   (aref (cdr friend) 7))
873
874 (defun mixi-friend-blood-type (friend)
875   "Return the blood type of FRIEND."
876   (unless (mixi-friend-p friend)
877     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
878   (mixi-realize-friend friend)
879   (aref (cdr friend) 8))
880
881 (defun mixi-friend-birthplace (friend)
882   "Return the birthplace of FRIEND."
883   (unless (mixi-friend-p friend)
884     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
885   (mixi-realize-friend friend)
886   (aref (cdr friend) 9))
887
888 (defun mixi-friend-hobby (friend)
889   "Return the hobby of FRIEND."
890   (unless (mixi-friend-p friend)
891     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
892   (mixi-realize-friend friend)
893   (aref (cdr friend) 10))
894
895 (defun mixi-friend-job (friend)
896   "Return the job of FRIEND."
897   (unless (mixi-friend-p friend)
898     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
899   (mixi-realize-friend friend)
900   (aref (cdr friend) 11))
901
902 (defun mixi-friend-organization (friend)
903   "Return the organization of FRIEND."
904   (unless (mixi-friend-p friend)
905     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
906   (mixi-realize-friend friend)
907   (aref (cdr friend) 12))
908
909 (defun mixi-friend-profile (friend)
910   "Return the pforile of FRIEND."
911   (unless (mixi-friend-p friend)
912     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
913   (mixi-realize-friend friend)
914   (aref (cdr friend) 13))
915
916 (defun mixi-friend-set-nick (friend nick)
917   "Set the nick of FRIEND."
918   (unless (mixi-friend-p friend)
919     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
920   (aset (cdr friend) 2 nick))
921
922 (defun mixi-friend-set-name (friend name)
923   "Set the name of FRIEND."
924   (unless (mixi-friend-p friend)
925     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
926   (aset (cdr friend) 3 name))
927
928 (defun mixi-friend-set-sex (friend sex)
929   "Set the sex of FRIEND."
930   (unless (mixi-friend-p friend)
931     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
932   (aset (cdr friend) 4 sex))
933
934 (defun mixi-friend-set-address (friend address)
935   "Set the address of FRIEND."
936   (unless (mixi-friend-p friend)
937     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
938   (aset (cdr friend) 5 address))
939
940 (defun mixi-friend-set-age (friend age)
941   "Set the age of FRIEND."
942   (unless (mixi-friend-p friend)
943     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
944   (aset (cdr friend) 6 age))
945
946 (defun mixi-friend-set-birthday (friend birthday)
947   "Set the birthday of FRIEND."
948   (unless (mixi-friend-p friend)
949     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
950   (aset (cdr friend) 7 birthday))
951
952 (defun mixi-friend-set-blood-type (friend blood-type)
953   "Set the blood type of FRIEND."
954   (unless (mixi-friend-p friend)
955     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
956   (aset (cdr friend) 8 blood-type))
957
958 (defun mixi-friend-set-birthplace (friend birthplace)
959   "Set the birthplace of FRIEND."
960   (unless (mixi-friend-p friend)
961     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
962   (aset (cdr friend) 9 birthplace))
963
964 (defun mixi-friend-set-hobby (friend hobby)
965   "Set the hobby of FRIEND."
966   (unless (mixi-friend-p friend)
967     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
968   (aset (cdr friend) 10 hobby))
969
970 (defun mixi-friend-set-job (friend job)
971   "Set the job of FRIEND."
972   (unless (mixi-friend-p friend)
973     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
974   (aset (cdr friend) 11 job))
975
976 (defun mixi-friend-set-organization (friend organization)
977   "Set the organization of FRIEND."
978   (unless (mixi-friend-p friend)
979     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
980   (aset (cdr friend) 12 organization))
981
982 (defun mixi-friend-set-profile (friend profile)
983   "Set the profile of FRIEND."
984   (unless (mixi-friend-p friend)
985     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
986   (aset (cdr friend) 13 profile))
987
988 (defmacro mixi-friend-list-page (&optional friend)
989   `(concat "/list_friend.pl?page=%d"
990            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
991
992 (defconst mixi-friend-list-id-regexp
993   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
994 (defconst mixi-friend-list-nick-regexp
995   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
996
997 (defun mixi-get-friends (&rest friend-or-range)
998   "Get friends of FRIEND."
999   (when (> (length friend-or-range) 2)
1000     (signal 'wrong-number-of-arguments (list 'mixi-get-friends
1001                                              (length friend-or-range))))
1002   (let ((friend (nth 0 friend-or-range))
1003         (range (nth 1 friend-or-range)))
1004     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1005       (setq friend (nth 1 friend-or-range))
1006       (setq range (nth 0 friend-or-range)))
1007     (unless (or (null friend) (mixi-friend-p friend))
1008       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1009     (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
1010                                        mixi-friend-list-id-regexp
1011                                        range))
1012           (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
1013                                          mixi-friend-list-nick-regexp
1014                                          range)))
1015       (let ((index 0)
1016             ret)
1017         (while (< index (length ids))
1018           (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
1019                                             (nth 0 (nth index nicks))) ret))
1020           (incf index))
1021         (reverse ret)))))
1022
1023 ;; Favorite.
1024 (defmacro mixi-favorite-list-page ()
1025   `(concat "/list_bookmark.pl?page=%d"))
1026
1027 (defconst mixi-favorite-list-id-regexp
1028   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
1029 (defconst mixi-favorite-list-nick-regexp
1030   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
1031 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
1032
1033 (defun mixi-get-favorites (&optional range)
1034   "Get favorites."
1035   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
1036                                      mixi-favorite-list-id-regexp
1037                                      range))
1038         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
1039                                        mixi-favorite-list-nick-regexp
1040                                        range)))
1041     (let ((index 0)
1042           ret)
1043       (while (< index (length ids))
1044         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
1045                                           (nth 0 (nth index nicks))) ret))
1046         (incf index))
1047       (reverse ret))))
1048
1049 ;; Log object.
1050 (defun mixi-make-log (friend time)
1051   "Return a log object."
1052   (cons 'mixi-log (vector friend time)))
1053
1054 (defmacro mixi-log-p (log)
1055   `(eq (mixi-object-class ,log) 'mixi-log))
1056
1057 (defun mixi-log-friend (log)
1058   "Return the friend of LOG."
1059   (unless (mixi-log-p log)
1060     (signal 'wrong-type-argument (list 'mixi-log-p log)))
1061   (aref (cdr log) 0))
1062
1063 (defun mixi-log-time (log)
1064   "Return the time of LOG."
1065   (unless (mixi-log-p log)
1066     (signal 'wrong-type-argument (list 'mixi-log-p log)))
1067   (aref (cdr log) 1))
1068
1069 (defmacro mixi-log-list-page ()
1070   `(concat "/show_log.pl"))
1071
1072 (defconst mixi-log-list-regexp
1073   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a></li>")
1074
1075 (defun mixi-get-logs (&optional range)
1076   "Get logs."
1077   (let ((items (mixi-get-matched-items (mixi-log-list-page)
1078                                        mixi-log-list-regexp
1079                                        range)))
1080     (mapcar (lambda (item)
1081               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
1082                              (encode-time 0
1083                                           (string-to-number (nth 4 item))
1084                                           (string-to-number (nth 3 item))
1085                                           (string-to-number (nth 2 item))
1086                                           (string-to-number (nth 1 item))
1087                                           (string-to-number (nth 0 item)))))
1088             items)))
1089
1090 ;; Diary object.
1091 (defvar mixi-diary-cache (make-hash-table :test 'equal))
1092 (defun mixi-make-diary (owner id &optional time title content)
1093   "Return a diary object."
1094   (let ((owner (or owner (mixi-make-me))))
1095     (mixi-make-cache (list (mixi-friend-id owner) id)
1096                      (cons 'mixi-diary (vector nil owner id time title
1097                                                content))
1098                      mixi-diary-cache)))
1099
1100 (defconst mixi-diary-url-regexp
1101   "/view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)")
1102
1103 (defun mixi-make-diary-from-url (url)
1104   "Return a diary object from URL."
1105   (when (string-match mixi-diary-url-regexp url)
1106     (let ((id (match-string 1 url))
1107           (owner-id (match-string 2 url)))
1108       (mixi-make-diary (mixi-make-friend owner-id) id))))
1109
1110 (defmacro mixi-diary-p (diary)
1111   `(eq (mixi-object-class ,diary) 'mixi-diary))
1112
1113 (defmacro mixi-diary-page (diary)
1114   `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
1115            "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
1116
1117 (defconst mixi-diary-closed-regexp
1118   "<td>ͧ¿Í\\(¤Îͧ¿Í\\)?¤Þ¤Ç¸ø³«¤Î¤¿¤áÆɤळ¤È¤¬½ÐÍè¤Þ¤»¤ó¡£</td></tr>")
1119 (defconst mixi-diary-owner-nick-regexp
1120   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+?\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
1121 (defconst mixi-diary-time-regexp
1122   "<td \\(align\\|ALIGN\\)=\"?center\"? \\(rowspan\\|ROWSPAN\\)=\"?[23]\"? \\(nowrap=\"nowrap\"\\|NOWRAP\\) \\(width\\|WIDTH\\)=\"?95\"? bgcolor=\"?#FFD8B0\"?>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(<br />\\|<br>\\)\\([0-9]+\\):\\([0-9]+\\)</td>")
1123 (defconst mixi-diary-title-regexp
1124   "<td \\(bgcolor\\|BGCOLOR\\)=\"?#FFF4E0\"? width=\"?430\"?>&nbsp;\\([^<]+\\)</td>")
1125 (defconst mixi-diary-content-regexp
1126   "<td \\(class\\|CLASS\\)=\"?h12\"?>\\(.*\\)</td>")
1127
1128 (defun mixi-realize-diary (diary)
1129   "Realize a DIARY."
1130   ;; FIXME: Check a expiration of cache?
1131   (unless (mixi-object-realized-p diary)
1132     (with-mixi-retrieve (mixi-diary-page diary)
1133       (unless (string-match mixi-diary-closed-regexp buffer)
1134         (if (string-match mixi-diary-owner-nick-regexp buffer)
1135             (mixi-friend-set-nick (mixi-diary-owner diary)
1136                                   (match-string 1 buffer))
1137           (mixi-realization-error 'cannot-find-owner-nick diary))
1138         (if (string-match mixi-diary-time-regexp buffer)
1139             (mixi-diary-set-time
1140              diary (encode-time 0 (string-to-number (match-string 10 buffer))
1141                                 (string-to-number (match-string 9 buffer))
1142                                 (string-to-number (match-string 7 buffer))
1143                                 (string-to-number (match-string 6 buffer))
1144                                 (string-to-number (match-string 5 buffer))))
1145           (mixi-realization-error 'cannot-find-time diary))
1146         (if (string-match mixi-diary-title-regexp buffer)
1147             (mixi-diary-set-title diary (match-string 2 buffer))
1148           (mixi-realization-error 'cannot-find-title diary))
1149         (if (string-match mixi-diary-content-regexp buffer)
1150             (mixi-diary-set-content diary (match-string 2 buffer))
1151           (mixi-realization-error 'cannot-find-content diary))))
1152     (mixi-object-touch diary)))
1153
1154 (defun mixi-diary-owner (diary)
1155   "Return the owner of DIARY."
1156   (unless (mixi-diary-p diary)
1157     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1158   (aref (cdr diary) 1))
1159
1160 (defun mixi-diary-id (diary)
1161   "Return the id of DIARY."
1162   (unless (mixi-diary-p diary)
1163     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1164   (aref (cdr diary) 2))
1165
1166 (defun mixi-diary-time (diary)
1167   "Return the time of DIARY."
1168   (unless (mixi-diary-p diary)
1169     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1170   (unless (aref (cdr diary) 3)
1171     (mixi-realize-diary diary))
1172   (aref (cdr diary) 3))
1173
1174 (defun mixi-diary-title (diary)
1175   "Return the title of DIARY."
1176   (unless (mixi-diary-p diary)
1177     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1178   (unless (aref (cdr diary) 4)
1179     (mixi-realize-diary diary))
1180   (aref (cdr diary) 4))
1181
1182 (defun mixi-diary-content (diary)
1183   "Return the content of DIARY."
1184   (unless (mixi-diary-p diary)
1185     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1186   (mixi-realize-diary diary)
1187   (aref (cdr diary) 5))
1188
1189 (defun mixi-diary-set-time (diary time)
1190   "Set the time of DIARY."
1191   (unless (mixi-diary-p diary)
1192     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1193   (aset (cdr diary) 3 time))
1194
1195 (defun mixi-diary-set-title (diary title)
1196   "Set the title of DIARY."
1197   (unless (mixi-diary-p diary)
1198     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1199   (aset (cdr diary) 4 title))
1200
1201 (defun mixi-diary-set-content (diary content)
1202   "Set the content of DIARY."
1203   (unless (mixi-diary-p diary)
1204     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
1205   (aset (cdr diary) 5 content))
1206
1207 (defmacro mixi-diary-list-page (&optional friend)
1208   `(concat "/list_diary.pl?page=%d"
1209            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1210
1211 (defconst mixi-diary-list-regexp
1212   "<tr VALIGN=top>
1213 <td ALIGN=center ROWSPAN=3 NOWRAP bgcolor=#F2DDB7><font COLOR=#996600>\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</font>\\(<br><input type=\"checkbox\" name=\"diary_id\" value=\"[0-9]+\">\\|\\)</td>
1214 <td bgcolor=\"#FFF4E0\">&nbsp;<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">\\(.*\\)</a></td>")
1215
1216 (defun mixi-get-diaries (&rest friend-or-range)
1217   "Get diaries of FRIEND."
1218   (when (> (length friend-or-range) 2)
1219     (signal 'wrong-number-of-arguments
1220             (list 'mixi-get-diaries (length friend-or-range))))
1221   (let ((friend (nth 0 friend-or-range))
1222         (range (nth 1 friend-or-range)))
1223     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1224       (setq friend (nth 1 friend-or-range))
1225       (setq range (nth 0 friend-or-range)))
1226     (unless (or (null friend) (mixi-friend-p friend))
1227       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1228     (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
1229                                          mixi-diary-list-regexp
1230                                          range))
1231           (year (nth 5 (decode-time (current-time))))
1232           (month (nth 4 (decode-time (current-time)))))
1233       (mapcar (lambda (item)
1234                 (let ((month-of-item (string-to-number (nth 0 item))))
1235                   (when (> month-of-item month)
1236                     (decf year))
1237                   (setq month month-of-item)
1238                   (mixi-make-diary friend (nth 5 item)
1239                                    (encode-time
1240                                     0 (string-to-number (nth 3 item))
1241                                     (string-to-number (nth 2 item))
1242                                     (string-to-number (nth 1 item))
1243                                     month year)
1244                                    (nth 6 item))))
1245               items))))
1246
1247 (defmacro mixi-new-diary-list-page ()
1248   `(concat "/new_friend_diary.pl?page=%d"))
1249
1250 (defconst mixi-new-diary-list-regexp
1251   "<td WIDTH=180><img src=http://img\\.mixi\\.jp/img/pen\\.gif ALIGN=left WIDTH=14 HEIGHT=16>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td>
1252 <td WIDTH=450><a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>\\(.+\\)</a> (\\(.*\\)) ")
1253
1254 (defun mixi-get-new-diaries (&optional range)
1255   "Get new diaries."
1256   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
1257                                        mixi-new-diary-list-regexp
1258                                        range)))
1259     (mapcar (lambda (item)
1260               (mixi-make-diary (mixi-make-friend (nth 6 item) (nth 8 item))
1261                                (nth 5 item)
1262                                (encode-time
1263                                 0 (string-to-number (nth 4 item))
1264                                 (string-to-number (nth 3 item))
1265                                 (string-to-number (nth 2 item))
1266                                 (string-to-number (nth 1 item))
1267                                 (string-to-number (nth 0 item)))
1268                                (nth 7 item)))
1269             items)))
1270
1271 (defmacro mixi-search-diary-list-page (keyword)
1272   `(concat "/search_diary.pl?page=%d&submit=search&keyword="
1273              (mixi-url-encode-and-quote-percent-string ,keyword)))
1274
1275 (defconst mixi-search-diary-list-regexp
1276   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
1277 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.*\\)
1278
1279 </td></tr>
1280
1281 <tr>
1282 <td BGCOLOR=#FDF9F2><font COLOR=#996600>¥¿¥¤¥È¥ë</font></td>
1283 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>
1284
1285 <tr>
1286 <td BGCOLOR=#FDF9F2><font COLOR=#996600>ËÜ&nbsp;&nbsp;ʸ</font></td>
1287 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.*\\)</td></tr>
1288
1289
1290 <tr>
1291 <td NOWRAP BGCOLOR=#FDF9F2 WIDTH=80><font COLOR=#996600>ºîÀ®Æü»þ</font></td>
1292 <td BGCOLOR=#FFFFFF WIDTH=220>\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td>
1293 <td ALIGN=center BGCOLOR=#FDF9F2 width=250><a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)\"><img src=http://img\\.mixi\\.jp/img/shbtn\\.gif ALT=¾ÜºÙ¤ò¸«¤ë BORDER=0 WIDTH=104 HEIGHT=19></a></td></tr>
1294 </table>
1295 </td></tr></table>")
1296
1297 (defun mixi-search-diaries (keyword &optional range)
1298   (let ((items (mixi-get-matched-items (mixi-search-diary-list-page keyword)
1299                                        mixi-search-diary-list-regexp
1300                                        range))
1301         (year (nth 5 (decode-time (current-time))))
1302         (month (nth 4 (decode-time (current-time)))))
1303     (mapcar (lambda (item)
1304                 (let ((month-of-item (string-to-number (nth 3 item))))
1305                   (when (> month-of-item month)
1306                     (decf year))
1307                   (setq month month-of-item)
1308                   (mixi-make-diary (mixi-make-friend (nth 8 item) (nth 0 item))
1309                                    (nth 7 item)
1310                                    (encode-time
1311                                     0 (string-to-number (nth 6 item))
1312                                     (string-to-number (nth 5 item))
1313                                     (string-to-number (nth 4 item))
1314                                     month year)
1315                                    (nth 1 item)
1316                                    (nth 2 item))))
1317             items)))
1318
1319 (defmacro mixi-post-diary-page ()
1320   `(concat "/add_diary.pl"))
1321
1322 (defconst mixi-post-key-regexp
1323   "<input type=\"?hidden\"? name=\"?post_key\"? value=\"\\([a-z0-9]+\\)\">")
1324 (defconst mixi-post-succeed-regexp
1325   "<b>\\(ºîÀ®\\|½ñ¤­¹þ¤ß\\)¤¬´°Î»¤·¤Þ¤·¤¿¡£È¿±Ç¤Ë»þ´Ö¤¬¤«¤«¤ë¤³¤È¤¬¤¢¤ê¤Þ¤¹¤Î¤Ç¡¢É½¼¨¤µ¤ì¤Æ¤¤¤Ê¤¤¾ì¹ç¤Ï¾¯¡¹¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£</b>")
1326
1327 ;; FIXME: Support photos.
1328 (defun mixi-post-diary (title content)
1329   "Post a diary."
1330   (unless (stringp title)
1331     (signal 'wrong-type-argument (list 'stringp title)))
1332   (unless (stringp content)
1333     (signal 'wrong-type-argument (list 'stringp content)))
1334   (let ((fields `(("id" . ,(mixi-friend-id (mixi-make-me)))
1335                   ("diary_title" . ,title)
1336                   ("diary_body" . ,content)
1337                   ("submit" . "main")))
1338         post-key)
1339     (with-mixi-post-form (mixi-post-diary-page) fields
1340       (if (string-match mixi-post-key-regexp buffer)
1341           (setq post-key (match-string 1 buffer))
1342         (mixi-post-error 'cannot-find-key)))
1343     (setq fields `(("post_key" . ,post-key)
1344                    ("id" . ,(mixi-friend-id (mixi-make-me)))
1345                    ("diary_title" . ,title)
1346                    ("diary_body" . ,content)
1347                    ("submit" . "confirm")))
1348     (with-mixi-post-form (mixi-post-diary-page) fields
1349       (unless (string-match mixi-post-succeed-regexp buffer)
1350         (mixi-post-error 'cannot-find-succeed)))))
1351
1352 ;; Community object.
1353 (defvar mixi-community-cache (make-hash-table :test 'equal))
1354 (defun mixi-make-community (id &optional name birthday owner category members
1355                                open-level authority description)
1356   "Return a community object."
1357   (mixi-make-cache id (cons 'mixi-community (vector nil id name birthday owner
1358                                                     category members
1359                                                     open-level authority
1360                                                     description))
1361                    mixi-community-cache))
1362
1363 (defconst mixi-community-url-regexp
1364   "/view_community\\.pl\\?id=\\([0-9]+\\)")
1365
1366 (defun mixi-make-community-from-url (url)
1367   "Return a community object from URL."
1368   (when (string-match mixi-community-url-regexp url)
1369     (let ((id (match-string 1 url)))
1370       (mixi-make-community id))))
1371
1372 (defmacro mixi-community-p (community)
1373   `(eq (mixi-object-class ,community) 'mixi-community))
1374
1375 (defmacro mixi-community-page (community)
1376   `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
1377
1378 ;; FIXME: Not community specific.
1379 (defconst mixi-community-nodata-regexp
1380   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1381 (defconst mixi-community-name-regexp
1382   "<td WIDTH=345>\\(.*\\)</td></tr>")
1383 (defconst mixi-community-birthday-regexp
1384   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>³«ÀßÆü</font></td>\r
1385 <td WIDTH=345>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1386 ;; FIXME: Care when the owner has seceded.
1387 (defconst mixi-community-owner-regexp
1388   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>´ÉÍý¿Í</font></td>\r
1389 <td WIDTH=345>\r
1390 <table style=\"width: 100%;\"><tr><td>\r
1391 <a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\\(.*\\)</a>")
1392 (defconst mixi-community-category-regexp
1393   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\r
1394 <td WIDTH=345>\\([^<]+\\)</td>")
1395 (defconst mixi-community-members-regexp
1396   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\r
1397 <td WIDTH=345>\\([0-9]+\\)¿Í</td></tr>")
1398 (defconst mixi-community-open-level-regexp
1399   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>\r
1400 <td WIDTH=345>\\(.+\\)</td></tr>")
1401 (defconst mixi-community-authority-regexp
1402   "<td BGCOLOR=#F2DDB7 WIDTH=80><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\r
1403 <td WIDTH=345>\\(.+\\)</td></tr>")
1404 (defconst mixi-community-description-regexp
1405   "<td CLASS=h120 WIDTH=345>\\(.+\\)</td>")
1406
1407 (defun mixi-realize-community (community)
1408   "Realize a COMMUNITY."
1409   ;; FIXME: Check a expiration of cache?
1410   (unless (mixi-object-realized-p community)
1411     (with-mixi-retrieve (mixi-community-page community)
1412       (if (string-match mixi-community-nodata-regexp buffer)
1413           ;; FIXME: Set all members?
1414           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
1415         (if (string-match mixi-community-name-regexp buffer)
1416             (mixi-community-set-name community (match-string 1 buffer))
1417           (mixi-realization-error 'cannot-find-name community))
1418         (if (string-match mixi-community-birthday-regexp buffer)
1419             (mixi-community-set-birthday
1420              community
1421              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
1422                           (string-to-number (match-string 2 buffer))
1423                           (string-to-number (match-string 1 buffer))))
1424           (mixi-realization-error 'cannot-find-birthday community))
1425         (if (string-match mixi-community-owner-regexp buffer)
1426             (if (string= (match-string 1 buffer) "home.pl")
1427                 (mixi-community-set-owner community (mixi-make-me))
1428               (mixi-community-set-owner
1429                community (mixi-make-friend (match-string 2 buffer)
1430                                            (match-string 3 buffer))))
1431           (mixi-realization-error 'cannot-find-owner community))
1432         (if (string-match mixi-community-category-regexp buffer)
1433             (mixi-community-set-category community (match-string 1 buffer))
1434           (mixi-realization-error 'cannot-find-category community))
1435         (if (string-match mixi-community-members-regexp buffer)
1436             (mixi-community-set-members
1437              community (string-to-number (match-string 1 buffer)))
1438           (mixi-realization-error 'cannot-find-members community))
1439         (if (string-match mixi-community-open-level-regexp buffer)
1440             (mixi-community-set-open-level community (match-string 1 buffer))
1441           (mixi-realization-error 'cannot-find-open-level community))
1442         (if (string-match mixi-community-authority-regexp buffer)
1443             (mixi-community-set-authority community (match-string 1 buffer))
1444           (mixi-realization-error 'cannot-find-authority community))
1445         (if (string-match mixi-community-description-regexp buffer)
1446             (mixi-community-set-description community (match-string 1 buffer))
1447           (mixi-realization-error 'cannot-find-description community))))
1448     (mixi-object-touch community)))
1449
1450 (defun mixi-community-id (community)
1451   "Return the id of COMMUNITY."
1452   (unless (mixi-community-p community)
1453     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1454   (aref (cdr community) 1))
1455
1456 (defun mixi-community-name (community)
1457   "Return the name of COMMUNITY."
1458   (unless (mixi-community-p community)
1459     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1460   (unless (aref (cdr community) 2)
1461     (mixi-realize-community community))
1462   (aref (cdr community) 2))
1463
1464 (defun mixi-community-birthday (community)
1465   "Return the birthday of COMMUNITY."
1466   (unless (mixi-community-p community)
1467     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1468   (mixi-realize-community community)
1469   (aref (cdr community) 3))
1470
1471 (defun mixi-community-owner (community)
1472   "Return the owner of COMMUNITY."
1473   (unless (mixi-community-p community)
1474     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1475   (mixi-realize-community community)
1476   (aref (cdr community) 4))
1477
1478 (defun mixi-community-category (community)
1479   "Return the category of COMMUNITY."
1480   (unless (mixi-community-p community)
1481     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1482   (mixi-realize-community community)
1483   (aref (cdr community) 5))
1484
1485 (defun mixi-community-members (community)
1486   "Return the members of COMMUNITY."
1487   (unless (mixi-community-p community)
1488     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1489   (mixi-realize-community community)
1490   (aref (cdr community) 6))
1491
1492 (defun mixi-community-open-level (community)
1493   "Return the open-level of COMMUNITY."
1494   (unless (mixi-community-p community)
1495     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1496   (mixi-realize-community community)
1497   (aref (cdr community) 7))
1498
1499 (defun mixi-community-authority (community)
1500   "Return the authority of COMMUNITY."
1501   (unless (mixi-community-p community)
1502     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1503   (mixi-realize-community community)
1504   (aref (cdr community) 8))
1505
1506 (defun mixi-community-description (community)
1507   "Return the description of COMMUNITY."
1508   (unless (mixi-community-p community)
1509     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1510   (mixi-realize-community community)
1511   (aref (cdr community) 9))
1512
1513 (defun mixi-community-set-name (community name)
1514   "Set the name of COMMUNITY."
1515   (unless (mixi-community-p community)
1516     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1517   (aset (cdr community) 2 name))
1518
1519 (defun mixi-community-set-birthday (community birthday)
1520   "Set the birthday of COMMUNITY."
1521   (unless (mixi-community-p community)
1522     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1523   (aset (cdr community) 3 birthday))
1524
1525 (defun mixi-community-set-owner (community owner)
1526   "Set the owner of COMMUNITY."
1527   (unless (mixi-community-p community)
1528     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1529   (unless (mixi-friend-p owner)
1530     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1531   (aset (cdr community) 4 owner))
1532
1533 (defun mixi-community-set-category (community category)
1534   "Set the category of COMMUNITY."
1535   (unless (mixi-community-p community)
1536     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1537   (aset (cdr community) 5 category))
1538
1539 (defun mixi-community-set-members (community members)
1540   "Set the name of COMMUNITY."
1541   (unless (mixi-community-p community)
1542     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1543   (aset (cdr community) 6 members))
1544
1545 (defun mixi-community-set-open-level (community open-level)
1546   "Set the name of COMMUNITY."
1547   (unless (mixi-community-p community)
1548     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1549   (aset (cdr community) 7 open-level))
1550
1551 (defun mixi-community-set-authority (community authority)
1552   "Set the name of COMMUNITY."
1553   (unless (mixi-community-p community)
1554     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1555   (aset (cdr community) 8 authority))
1556
1557 (defun mixi-community-set-description (community description)
1558   "Set the name of COMMUNITY."
1559   (unless (mixi-community-p community)
1560     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1561   (aset (cdr community) 9 description))
1562
1563 (defmacro mixi-community-list-page (&optional friend)
1564   `(concat "/list_community.pl?page=%d"
1565            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
1566
1567 (defconst mixi-community-list-id-regexp
1568   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1569 (defconst mixi-community-list-name-regexp
1570   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1571
1572 (defun mixi-get-communities (&rest friend-or-range)
1573   "Get communities of FRIEND."
1574   (when (> (length friend-or-range) 2)
1575     (signal 'wrong-number-of-arguments
1576             (list 'mixi-get-communities (length friend-or-range))))
1577   (let ((friend (nth 0 friend-or-range))
1578         (range (nth 1 friend-or-range)))
1579     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
1580       (setq friend (nth 1 friend-or-range))
1581       (setq range (nth 0 friend-or-range)))
1582     (unless (or (null friend) (mixi-friend-p friend))
1583       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1584     (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1585                                        mixi-community-list-id-regexp
1586                                        range))
1587           (names (mixi-get-matched-items (mixi-community-list-page friend)
1588                                          mixi-community-list-name-regexp
1589                                          range)))
1590       (let ((index 0)
1591             ret)
1592         (while (< index (length ids))
1593           (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1594                                                (nth 0 (nth index names))) ret))
1595           (incf index))
1596         (reverse ret)))))
1597
1598 (defmacro mixi-search-community-list-page (keyword)
1599   `(concat "/search_community.pl?page=%d&&sort=date&type=com&submit=main"
1600            "&keyword=" (mixi-url-encode-and-quote-percent-string ,keyword)
1601            "&category_id=0"))
1602
1603 (defconst mixi-search-community-list-regexp
1604   "<td WIDTH=90 VALIGN=top ROWSPAN=4 ALIGN=center background=http://img\\.mixi\\.jp/img/bg_line\\.gif><a href=\"view_community\\.pl\\?id=\\([0-9]+\\)\"><img SRC=\"http://img-c[0-9]+\\.mixi\\.jp/photo/comm/[^.]+\\.jpg\" VSPACE=3 border=0></a></td>
1605 <td NOWRAP WIDTH=90 BGCOLOR=#FDF9F2><font COLOR=#996600>¥³¥ß¥å¥Ë¥Æ¥£Ì¾</font></td>
1606 <td COLSPAN=2 WIDTH=370 BGCOLOR=#FFFFFF>\\([^<]+\\)</td></tr>")
1607
1608 ;; FIXME: Support category.
1609 (defun mixi-search-communities (keyword &optional range)
1610   (let ((items (mixi-get-matched-items (mixi-search-community-list-page
1611                                         keyword)
1612                                        mixi-search-community-list-regexp
1613                                        range)))
1614     (mapcar (lambda (item)
1615               (mixi-make-community (nth 0 item) (nth 1 item)))
1616             items)))
1617
1618 ;; Topic object.
1619 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1620 (defun mixi-make-topic (community id &optional time title owner content)
1621   "Return a topic object."
1622   (mixi-make-cache (list (mixi-community-id community) id)
1623                    (cons 'mixi-topic (vector nil community id time title owner
1624                                              content))
1625                    mixi-topic-cache))
1626
1627 (defconst mixi-topic-url-regexp
1628   "/view_bbs\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1629
1630 (defun mixi-make-topic-from-url (url)
1631   "Return a topic object from URL."
1632   (when (string-match mixi-topic-url-regexp url)
1633     (let ((id (match-string 1 url))
1634           (community-id (match-string 3 url)))
1635       (mixi-make-topic (mixi-make-community community-id) id))))
1636
1637 (defmacro mixi-topic-p (topic)
1638   `(eq (mixi-object-class ,topic) 'mixi-topic))
1639
1640 (defmacro mixi-topic-page (topic)
1641   `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
1642            "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
1643
1644 (defconst mixi-topic-community-regexp
1645   "<td width=\"595\" background=\"http://img\\.mixi\\.jp/img/bg_w\\.gif\"><b>\\[\\(.+\\)\\] ¥È¥Ô¥Ã¥¯</b></td>")
1646 (defconst mixi-topic-time-regexp
1647   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1648 (defconst mixi-topic-title-regexp
1649   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1650 (defconst mixi-topic-owner-regexp
1651   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*?\\)\\(¤µ¤ó\\)?</a>")
1652 (defconst mixi-topic-content-regexp
1653   "<table width=\"500\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\"><tr><td class=\"h120\"><table><tr>\\(<td width=\"130\" height=\"140\" align=\"center\" valign=\"middle\"><a href=\"javascript:void(0)\" onClick=\"MM_openBrWindow('show_bbs_picture\\.pl\\?id=[0-9]+&comm_id=[0-9]+&number=[0-9]+','pict','width=680,height=660,toolbar=no,scrollbars=yes,left=5,top=5')\"><img src=\"http://ic[0-9]+\\.mixi\\.jp/[^.]+\\.jpg\" border=\"0\"></a></td>\n\\)*</tr></table>\\(.+\\)</td></tr></table>")
1654
1655 (defun mixi-realize-topic (topic)
1656   "Realize a TOPIC."
1657   ;; FIXME: Check a expiration of cache?
1658   (unless (mixi-object-realized-p topic)
1659     (with-mixi-retrieve (mixi-topic-page topic)
1660       (if (string-match mixi-topic-community-regexp buffer)
1661           (mixi-community-set-name (mixi-topic-community topic)
1662                                    (match-string 1 buffer))
1663         (mixi-realization-error 'cannot-find-community topic))
1664       (if (string-match mixi-topic-time-regexp buffer)
1665           (mixi-topic-set-time
1666            topic (encode-time 0 (string-to-number (match-string 5 buffer))
1667                               (string-to-number (match-string 4 buffer))
1668                               (string-to-number (match-string 3 buffer))
1669                               (string-to-number (match-string 2 buffer))
1670                               (string-to-number (match-string 1 buffer))))
1671         (mixi-realization-error 'cannot-find-time topic))
1672       (if (string-match mixi-topic-title-regexp buffer)
1673           (mixi-topic-set-title topic (match-string 1 buffer))
1674         (mixi-realization-error 'cannot-find-title topic))
1675       (if (string-match mixi-topic-owner-regexp buffer)
1676           (mixi-topic-set-owner topic
1677                                 (mixi-make-friend (match-string 1 buffer)
1678                                                   (match-string 2 buffer)))
1679         (mixi-realization-error 'cannot-find-owner topic))
1680       (if (string-match mixi-topic-content-regexp buffer)
1681           (mixi-topic-set-content topic (match-string 2 buffer))
1682         (mixi-realization-error 'cannot-find-content topic)))
1683     (mixi-object-touch topic)))
1684
1685 (defun mixi-topic-community (topic)
1686   "Return the community of TOPIC."
1687   (unless (mixi-topic-p topic)
1688     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1689   (aref (cdr topic) 1))
1690
1691 (defun mixi-topic-id (topic)
1692   "Return the id of TOPIC."
1693   (unless (mixi-topic-p topic)
1694     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1695   (aref (cdr topic) 2))
1696
1697 (defun mixi-topic-time (topic)
1698   "Return the time of TOPIC."
1699   (unless (mixi-topic-p topic)
1700     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1701   (mixi-realize-topic topic)
1702   (aref (cdr topic) 3))
1703
1704 (defun mixi-topic-title (topic)
1705   "Return the title of TOPIC."
1706   (unless (mixi-topic-p topic)
1707     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1708   (mixi-realize-topic topic)
1709   (aref (cdr topic) 4))
1710
1711 (defun mixi-topic-owner (topic)
1712   "Return the owner of TOPIC."
1713   (unless (mixi-topic-p topic)
1714     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1715   (mixi-realize-topic topic)
1716   (aref (cdr topic) 5))
1717
1718 (defun mixi-topic-content (topic)
1719   "Return the content of TOPIC."
1720   (unless (mixi-topic-p topic)
1721     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1722   (mixi-realize-topic topic)
1723   (aref (cdr topic) 6))
1724
1725 (defun mixi-topic-set-time (topic time)
1726   "Set the time of TOPIC."
1727   (unless (mixi-topic-p topic)
1728     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1729   (aset (cdr topic) 3 time))
1730
1731 (defun mixi-topic-set-title (topic title)
1732   "Set the title of TOPIC."
1733   (unless (mixi-topic-p topic)
1734     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1735   (aset (cdr topic) 4 title))
1736
1737 (defun mixi-topic-set-owner (topic owner)
1738   "Set the owner of TOPIC."
1739   (unless (mixi-topic-p topic)
1740     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1741   (unless (mixi-friend-p owner)
1742     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1743   (aset (cdr topic) 5 owner))
1744
1745 (defun mixi-topic-set-content (topic content)
1746   "Set the content of TOPIC."
1747   (unless (mixi-topic-p topic)
1748     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1749   (aset (cdr topic) 6 content))
1750
1751 (defmacro mixi-post-topic-page (community)
1752   `(concat "/add_bbs.pl?id=" (mixi-community-id community)))
1753
1754 ;; FIXME: Support photos.
1755 (defun mixi-post-topic (community title content)
1756   "Post a topic to COMMUNITY."
1757   (unless (mixi-community-p community)
1758     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1759   (unless (stringp title)
1760     (signal 'wrong-type-argument (list 'stringp title)))
1761   (unless (stringp content)
1762     (signal 'wrong-type-argument (list 'stringp content)))
1763   (let ((fields `(("bbs_title" . ,title)
1764                   ("bbs_body" . ,content)
1765                   ("submit" . "main")))
1766         post-key)
1767     (with-mixi-post-form (mixi-post-topic-page community) fields
1768       (if (string-match mixi-post-key-regexp buffer)
1769           (setq post-key (match-string 1 buffer))
1770         (mixi-post-error 'cannot-find-key community)))
1771     (setq fields `(("post_key" . ,post-key)
1772                    ("bbs_title" . ,title)
1773                    ("bbs_body" . ,content)
1774                    ("submit" . "confirm")))
1775     (with-mixi-post-form (mixi-post-topic-page community) fields
1776       (unless (string-match mixi-post-succeed-regexp buffer)
1777         (mixi-post-error 'cannot-find-succeed community)))))
1778
1779 ;; Event object.
1780 (defvar mixi-event-cache (make-hash-table :test 'equal))
1781 (defun mixi-make-event (community id &optional time title owner date place
1782                                   detail limit members)
1783   "Return a event object."
1784   (mixi-make-cache (list (mixi-community-id community) id)
1785                    (cons 'mixi-event (vector nil community id time title owner
1786                                              date place detail limit members))
1787                    mixi-event-cache))
1788
1789 (defconst mixi-event-url-regexp
1790   "/view_event\\.pl\\?id=\\([0-9]+\\)\\(&comment_count=[0-9]+\\)?&comm_id=\\([0-9]+\\)")
1791
1792 (defun mixi-make-event-from-url (url)
1793   "Return a event object from URL."
1794   (when (string-match mixi-event-url-regexp url)
1795     (let ((id (match-string 1 url))
1796           (community-id (match-string 3 url)))
1797       (mixi-make-event (mixi-make-community community-id) id))))
1798
1799 (defmacro mixi-event-p (event)
1800   `(eq (mixi-object-class ,event) 'mixi-event))
1801
1802 (defmacro mixi-event-page (event)
1803   `(concat "/view_event.pl?id=" (mixi-event-id ,event)
1804            "&comm_id=" (mixi-community-id (mixi-event-community ,event))))
1805
1806 (defconst mixi-event-community-regexp
1807   "<td WIDTH=595 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b>\\[\\(.+\\)\\] ¥¤¥Ù¥ó¥È</b></td>")
1808 (defconst mixi-event-time-regexp
1809   "<td ROWSPAN=11 \\(BGCOLOR\\|bgcolor\\)=#FFD8B0 \\(ALIGN\\|align\\)=center \\(VALIGN\\|Valign\\)=top WIDTH=110>
1810 ?\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1811 ?\\([0-9]+\\):\\([0-9]+\\)</td>")
1812 (defconst mixi-event-title-regexp
1813   "<td bgcolor=#FFF4E0\\( width=410\\)?>&nbsp;\\([^<]+\\)</td>")
1814 (defconst mixi-event-owner-regexp
1815   "<td \\(BGCOLOR\\|bgcolor\\)=#FDF9F2>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>")
1816 (defconst mixi-event-owner-seceded-regexp
1817   "<td \\(BGCOLOR\\|bgcolor\\)=#FDF9F2>&nbsp;\\((mixi Âà²ñºÑ)\\)")
1818 (defconst mixi-event-date-regexp
1819   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>³«ºÅÆü»þ</td>
1820 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1821 &nbsp;\\(.+\\)
1822 </td>")
1823 (defconst mixi-event-place-regexp
1824   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>³«ºÅ¾ì½ê</td>
1825 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1826 &nbsp;\\(.+\\)
1827 </td>")
1828 (defconst mixi-event-detail-regexp
1829   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>¾ÜºÙ</td>
1830 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)><table BORDER=0 CELLSPACING=0 CELLPADDING=5><tr><td CLASS=h120>\\(.+\\)</td></tr></table></td>")
1831 (defconst mixi-event-limit-regexp
1832   "<td \\(BGCOLOR\\|bgcolor\\)=\"?#\\(FFFFFF\\|ffffff\\)\"? \\(ALIGN\\|align\\)=\"?center\"? NOWRAP>Ê罸´ü¸Â</td>
1833 ?<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
1834 (defconst mixi-event-members-regexp
1835   "<td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\) \\(ALIGN\\|align\\)=center NOWRAP>»²²Ã¼Ô</td>
1836 <td \\(BGCOLOR\\|bgcolor\\)=#\\(FFFFFF\\|ffffff\\)>
1837
1838 ?
1839 ?<table BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=100%>
1840 <tr>
1841
1842 ?<td>&nbsp;\\(.+\\)</td>")
1843
1844 (defun mixi-realize-event (event)
1845   "Realize a EVENT."
1846   ;; FIXME: Check a expiration of cache?
1847   (unless (mixi-object-realized-p event)
1848     (with-mixi-retrieve (mixi-event-page event)
1849       (if (string-match mixi-event-community-regexp buffer)
1850           (mixi-community-set-name (mixi-event-community event)
1851                                    (match-string 1 buffer))
1852         (mixi-realization-error 'cannot-find-community event))
1853       (if (string-match mixi-event-time-regexp buffer)
1854           (mixi-event-set-time
1855            event (encode-time 0 (string-to-number (match-string 8 buffer))
1856                               (string-to-number (match-string 7 buffer))
1857                               (string-to-number (match-string 6 buffer))
1858                               (string-to-number (match-string 5 buffer))
1859                               (string-to-number (match-string 4 buffer))))
1860         (mixi-realization-error 'cannot-find-time event))
1861       (if (string-match mixi-event-title-regexp buffer)
1862           (mixi-event-set-title event (match-string 2 buffer))
1863         (mixi-realization-error 'cannot-find-title event))
1864       (if (string-match mixi-event-owner-regexp buffer)
1865           (mixi-event-set-owner event
1866                                 (mixi-make-friend (match-string 2 buffer)
1867                                                   (match-string 3 buffer)))
1868         (if (string-match mixi-event-owner-seceded-regexp buffer)
1869             (mixi-event-set-owner event
1870                                   (mixi-make-friend nil
1871                                                     (match-string 2 buffer)))
1872           (mixi-realization-error 'cannot-find-owner event)))
1873       (if (string-match mixi-event-date-regexp buffer)
1874           (mixi-event-set-date event (match-string 6 buffer))
1875         (mixi-realization-error 'cannot-find-date event))
1876       (if (string-match mixi-event-place-regexp buffer)
1877           (mixi-event-set-place event (match-string 6 buffer))
1878         (mixi-realization-error 'cannot-find-place event))
1879       (if (string-match mixi-event-detail-regexp buffer)
1880           (mixi-event-set-detail event (match-string 6 buffer))
1881         (mixi-realization-error 'cannot-find-detail event))
1882       (when (string-match mixi-event-limit-regexp buffer)
1883         (mixi-event-set-limit
1884          event (encode-time 0 0 0 (string-to-number (match-string 8 buffer))
1885                             (string-to-number (match-string 7 buffer))
1886                             (string-to-number (match-string 6 buffer)))))
1887       (if (string-match mixi-event-members-regexp buffer)
1888           (mixi-event-set-members event (match-string 6 buffer))
1889         (mixi-realization-error 'cannot-find-members event)))
1890     (mixi-object-touch event)))
1891
1892 (defun mixi-event-community (event)
1893   "Return the community of EVENT."
1894   (unless (mixi-event-p event)
1895     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1896   (aref (cdr event) 1))
1897
1898 (defun mixi-event-id (event)
1899   "Return the id of EVENT."
1900   (unless (mixi-event-p event)
1901     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1902   (aref (cdr event) 2))
1903
1904 (defun mixi-event-time (event)
1905   "Return the time of EVENT."
1906   (unless (mixi-event-p event)
1907     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1908   (mixi-realize-event event)
1909   (aref (cdr event) 3))
1910
1911 (defun mixi-event-title (event)
1912   "Return the title of EVENT."
1913   (unless (mixi-event-p event)
1914     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1915   (mixi-realize-event event)
1916   (aref (cdr event) 4))
1917
1918 (defun mixi-event-owner (event)
1919   "Return the owner of EVENT."
1920   (unless (mixi-event-p event)
1921     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1922   (mixi-realize-event event)
1923   (aref (cdr event) 5))
1924
1925 (defun mixi-event-date (event)
1926   "Return the date of EVENT."
1927   (unless (mixi-event-p event)
1928     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1929   (mixi-realize-event event)
1930   (aref (cdr event) 6))
1931
1932 (defun mixi-event-place (event)
1933   "Return the place of EVENT."
1934   (unless (mixi-event-p event)
1935     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1936   (mixi-realize-event event)
1937   (aref (cdr event) 7))
1938
1939 (defun mixi-event-detail (event)
1940   "Return the detail of EVENT."
1941   (unless (mixi-event-p event)
1942     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1943   (mixi-realize-event event)
1944   (aref (cdr event) 8))
1945
1946 (defun mixi-event-limit (event)
1947   "Return the limit of EVENT."
1948   (unless (mixi-event-p event)
1949     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1950   (mixi-realize-event event)
1951   (aref (cdr event) 9))
1952
1953 (defun mixi-event-members (event)
1954   "Return the members of EVENT."
1955   (unless (mixi-event-p event)
1956     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1957   (mixi-realize-event event)
1958   (aref (cdr event) 10))
1959
1960 (defun mixi-event-set-time (event time)
1961   "Set the time of EVENT."
1962   (unless (mixi-event-p event)
1963     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1964   (aset (cdr event) 3 time))
1965
1966 (defun mixi-event-set-title (event title)
1967   "Set the title of EVENT."
1968   (unless (mixi-event-p event)
1969     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1970   (aset (cdr event) 4 title))
1971
1972 (defun mixi-event-set-owner (event owner)
1973   "Set the owner of EVENT."
1974   (unless (mixi-event-p event)
1975     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1976   (unless (mixi-friend-p owner)
1977     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1978   (aset (cdr event) 5 owner))
1979
1980 (defun mixi-event-set-date (event date)
1981   "Set the date of EVENT."
1982   (unless (mixi-event-p event)
1983     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1984   (aset (cdr event) 6 date))
1985
1986 (defun mixi-event-set-place (event place)
1987   "Set the place of EVENT."
1988   (unless (mixi-event-p event)
1989     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1990   (aset (cdr event) 7 place))
1991
1992 (defun mixi-event-set-detail (event detail)
1993   "Set the detail of EVENT."
1994   (unless (mixi-event-p event)
1995     (signal 'wrong-type-argument (list 'mixi-event-p event)))
1996   (aset (cdr event) 8 detail))
1997
1998 (defun mixi-event-set-limit (event limit)
1999   "Set the limit of EVENT."
2000   (unless (mixi-event-p event)
2001     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2002   (aset (cdr event) 9 limit))
2003
2004 (defun mixi-event-set-members (event members)
2005   "Set the members of EVENT."
2006   (unless (mixi-event-p event)
2007     (signal 'wrong-type-argument (list 'mixi-event-p event)))
2008   (aset (cdr event) 10 members))
2009
2010 ;; Bbs object.
2011 (defconst mixi-bbs-list '(mixi-topic mixi-event))
2012
2013 (defmacro mixi-bbs-p (object)
2014   `(memq (mixi-object-class ,object) mixi-bbs-list))
2015
2016 (defun mixi-bbs-community (object)
2017   "Return the community of OBJECT."
2018   (unless (mixi-bbs-p object)
2019     (signal 'wrong-type-argument (list 'mixi-bbs-p object)))
2020   (let ((func (intern (concat mixi-object-prefix
2021                               (mixi-object-name object) "-community"))))
2022     (funcall func object)))
2023
2024 (defalias 'mixi-bbs-id 'mixi-object-id)
2025 (defalias 'mixi-bbs-time 'mixi-object-time)
2026 (defalias 'mixi-bbs-title 'mixi-object-title)
2027 (defalias 'mixi-bbs-owner 'mixi-object-owner)
2028 (defalias 'mixi-bbs-content 'mixi-object-content)
2029
2030 (defmacro mixi-bbs-list-page (community)
2031   `(concat "/list_bbs.pl?page=%d"
2032            "&id=" (mixi-community-id ,community)))
2033
2034 (defconst mixi-bbs-list-regexp
2035   "<a href=view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)")
2036
2037 (defun mixi-get-bbses (community &optional range)
2038   "Get bbese of COMMUNITY."
2039   (unless (mixi-community-p community)
2040     (signal 'wrong-type-argument (list 'mixi-community-p community)))
2041   (let ((items (mixi-get-matched-items (mixi-bbs-list-page community)
2042                                        mixi-bbs-list-regexp
2043                                        range)))
2044     (mapcar (lambda (item)
2045               (let ((name (nth 0 item)))
2046                 (when (string= name "bbs")
2047                   (setq name "topic"))
2048                 (let ((func (intern (concat "mixi-make-" name))))
2049                   (funcall func community (nth 1 item)))))
2050             items)))
2051
2052 (defmacro mixi-new-bbs-list-page ()
2053   `(concat "/new_bbs.pl?page=%d"))
2054
2055 (defconst mixi-new-bbs-list-regexp
2056   "<a href=\"view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
2057
2058 (defun mixi-get-new-bbses (&optional range)
2059   "Get new topics."
2060   (let ((items (mixi-get-matched-items (mixi-new-bbs-list-page)
2061                                        mixi-new-bbs-list-regexp
2062                                        range)))
2063     (mapcar (lambda (item)
2064               (let ((name (nth 0 item)))
2065                 (when (string= name "bbs")
2066                   (setq name "topic"))
2067                 (let ((func (intern (concat "mixi-make-" name))))
2068                   (funcall func (mixi-make-community (nth 2 item))
2069                            (nth 1 item)))))
2070             items)))
2071
2072 (defmacro mixi-search-bbs-list-page (keyword)
2073   `(concat "/search_topic.pl?page=%d&type=top&submit=search"
2074            "&keyword=" (mixi-url-encode-and-quote-percent-string ,keyword)
2075            "&community_id=0&category_id=0"))
2076
2077 (defconst mixi-search-bbs-list-regexp
2078   "<a href=\"view_\\(bbs\\|event\\)\\.pl\\?id=\\([0-9]+\\)&comm_id=\\([0-9]+\\)\"><img src=http://img\\.mixi\\.jp/img/shbtn\\.gif ALT=¾ÜºÙ¤ò¸«¤ë BORDER=0 WIDTH=104 HEIGHT=19></a>")
2079
2080 ;; FIXME: Support community and category.
2081 (defun mixi-search-bbses (keyword &optional range)
2082   (let ((items (mixi-get-matched-items (mixi-search-bbs-list-page keyword)
2083                                        mixi-search-bbs-list-regexp
2084                                        range)))
2085     (mapcar (lambda (item)
2086               (let ((name (nth 0 item)))
2087                 (when (string= name "bbs")
2088                   (setq name "topic"))
2089                 (let ((func (intern (concat "mixi-make-" name))))
2090                   (funcall func (mixi-make-community (nth 2 item))
2091                            (nth 1 item)))))
2092             items)))
2093
2094 ;; Comment object.
2095 (defun mixi-make-comment (parent owner time content)
2096   "Return a comment object."
2097   (cons 'mixi-comment (vector parent owner time content)))
2098
2099 (defmacro mixi-comment-p (comment)
2100   `(eq (mixi-object-class ,comment) 'mixi-comment))
2101
2102 (defun mixi-comment-parent (comment)
2103   "Return the parent of COMMENT."
2104   (unless (mixi-comment-p comment)
2105     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2106   (aref (cdr comment) 0))
2107
2108 (defun mixi-comment-owner (comment)
2109   "Return the owner of COMMENT."
2110   (unless (mixi-comment-p comment)
2111     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2112   (aref (cdr comment) 1))
2113
2114 (defun mixi-comment-time (comment)
2115   "Return the time of COMMENT."
2116   (unless (mixi-comment-p comment)
2117     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2118   (aref (cdr comment) 2))
2119
2120 (defun mixi-comment-content (comment)
2121   "Return the content of COMMENT."
2122   (unless (mixi-comment-p comment)
2123     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
2124   (aref (cdr comment) 3))
2125
2126 (defun mixi-diary-comment-list-page (diary)
2127   (concat "/view_diary.pl?full=1"
2128           "&id=" (mixi-diary-id diary)
2129           "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
2130
2131 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2132 (defconst mixi-diary-comment-list-regexp
2133 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
2134 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
2135 <input type=checkbox name=comment_id value=\".+\">
2136 \\|\\)
2137 </td>
2138 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
2139 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
2140 <tr>
2141 \\(<td>\\)
2142 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2143
2144 \\(<font color=\"#f2ddb7\">|</font> <a href=[^>]+>ºï½ü</a>
2145
2146 \\|\\)</td>
2147 </tr>
2148 </table>
2149 </td>
2150 </tr>
2151 <!-- [^ ]+ : start -->
2152 <tr>
2153 <td bgcolor=\"#ffffff\">
2154 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
2155 <tr>
2156 <td CLASS=h12>
2157 \\(.+\\)
2158 </td></tr></table>")
2159
2160 (defun mixi-topic-comment-list-page (topic)
2161   (concat "/view_bbs.pl?page=all"
2162           "&id=" (mixi-topic-id topic)
2163           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
2164
2165 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2166 (defconst mixi-topic-comment-list-regexp
2167   "<tr valign=\"top\">
2168 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
2169 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
2170 \\([0-9]+\\):\\([0-9]+\\)<br>
2171 \\(<input type=\"checkbox\" name=\"comment_id\" value=\".+\">
2172 \\|\\)</td>
2173 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
2174 <b>[^<]+</b>:</font>&nbsp;
2175 \\(
2176 \\|\\) *<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2177
2178 ?\\(
2179
2180 \\|<font color=\"#f2ddb7\">|&nbsp;</font><a href=\"delete_bbs_comment\\.pl\\?id=[0-9]+&comm_id=[0-9]+&comment_id=[0-9]+\">ºï½ü</a>
2181 \\|\\)</td>
2182 </tr>
2183 <tr>
2184 <td bgcolor=\"#ffffff\" align=\"center\">
2185 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
2186 <tr>
2187 <td class=\"h120\">
2188
2189 \\(.+\\)
2190 </td>
2191 </tr>
2192 </table>
2193 </td>
2194 </tr>")
2195
2196 (defun mixi-event-comment-list-page (event)
2197   (concat "/view_event.pl?page=all"
2198           "&id=" (mixi-event-id event)
2199           "&comm_id=" (mixi-community-id (mixi-event-community event))))
2200
2201 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
2202 (defconst mixi-event-comment-list-regexp
2203   "<tr>
2204 <td ROWSPAN=2 ALIGN=center BGCOLOR=#F2DDB7 WIDTH=110>
2205 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
2206 \\([0-9]+\\):\\([0-9]+\\)<br>
2207 \\(</td>\\)
2208 \\(<td BGCOLOR=#FDF9F2>\\)
2209 <font COLOR=#F8A448><b>[^<]+</b> :</font>
2210 <a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)</a>
2211
2212 \\(<font COLOR=#F2DDB7>|</font>
2213 <a href=\"delete_bbs_comment\\.pl\\?id=[0-9]+&comm_id=[0-9]+&comment_id=[0-9]+&type=event\">ºï½ü</a>
2214
2215 \\|\\)</td>
2216 </tr>
2217 <tr>
2218 <td ALIGN=center BGCOLOR=#FFFFFF>
2219 <table BORDER=0 CELLSPACING=0 CELLPADDING=5 WIDTH=500>
2220 <tr><td CLASS=h120>\\(.+\\)</td></tr>
2221 </table>
2222 </td>
2223 </tr>")
2224
2225 (defun mixi-get-comments (parent &optional range)
2226   "Get comments of PARENT."
2227   (unless (mixi-object-p parent)
2228     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
2229   (let* ((name (mixi-object-name parent))
2230          (list-page (intern (concat mixi-object-prefix name
2231                                     "-comment-list-page")))
2232          (regexp (eval (intern (concat mixi-object-prefix name
2233                                        "-comment-list-regexp")))))
2234     (let ((items (mixi-get-matched-items
2235                   (funcall list-page parent) regexp)))
2236       (let (list)
2237         (catch 'stop
2238           (mapc (lambda (item)
2239                   (when (and (numberp range)
2240                              (>= (length list) range))
2241                     (throw 'stop nil))
2242                   (setq list (cons item list)))
2243                 (reverse items)))
2244         (setq items (reverse list)))
2245       (mapcar (lambda (item)
2246                 (mixi-make-comment parent (mixi-make-friend
2247                                            (nth 7 item) (nth 8 item))
2248                                    (encode-time
2249                                     0
2250                                     (string-to-number (nth 4 item))
2251                                     (string-to-number (nth 3 item))
2252                                     (string-to-number (nth 2 item))
2253                                     (string-to-number (nth 1 item))
2254                                     (string-to-number (nth 0 item)))
2255                                    (nth 10 item)))
2256               items))))
2257
2258 (defmacro mixi-new-comment-list-page ()
2259   `(concat "/new_comment.pl?page=%d"))
2260
2261 (defconst mixi-new-comment-list-regexp
2262   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
2263
2264 (defun mixi-get-new-comments (&optional range)
2265   "Get new comments."
2266   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
2267                                        mixi-new-comment-list-regexp
2268                                        range)))
2269     (mapcar (lambda (item)
2270               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
2271             items)))
2272
2273 (defun mixi-post-diary-comment-page (diary)
2274   (concat "/add_comment.pl?&diary_id=" (mixi-diary-id diary)))
2275
2276 (defun mixi-post-topic-comment-page (topic)
2277   (concat "/add_bbs_comment.pl?id=" (mixi-topic-id topic)
2278           "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
2279
2280 (defun mixi-post-event-comment-page (event)
2281   (concat "/add_event_comment.pl?id=" (mixi-event-id event)
2282           "&comm_id=" (mixi-community-id (mixi-event-community event))))
2283
2284 ;; FIXME: Support photos.
2285 (defun mixi-post-comment (parent content)
2286   "Post a comment to PARENT."
2287   (unless (mixi-object-p parent)
2288     (signal 'wrong-type-argument (list 'mixi-object-p parent)))
2289   (unless (stringp content)
2290     (signal 'wrong-type-argument (list 'stringp content)))
2291   (let* ((name (mixi-object-name parent))
2292          (page (intern (concat mixi-object-prefix "post-" name
2293                                "-comment-page")))
2294          fields post-key)
2295     (if (mixi-diary-p parent)
2296         (setq fields
2297               `(("owner_id" . ,(mixi-friend-id (mixi-diary-owner parent)))
2298                 ("comment_body" . ,content)))
2299       (setq fields `(("comment" . ,content))))
2300     (with-mixi-post-form (funcall page parent) fields
2301       (if (string-match mixi-post-key-regexp buffer)
2302           (setq post-key (match-string 1 buffer))
2303         (mixi-post-error 'cannot-find-key parent)))
2304     (if (mixi-diary-p parent)
2305         (setq fields
2306               `(("post_key" . ,post-key)
2307                 ("owner_id" . ,(mixi-friend-id (mixi-diary-owner parent)))
2308                 ("comment_body" . ,content)
2309                 ("submit" . "confirm")))
2310       (setq fields `(("post_key" . ,post-key)
2311                      ("comment" . ,content)
2312                      ("submit" . "confirm"))))
2313     (with-mixi-post-form (funcall page parent) fields
2314       (unless (string-match mixi-post-succeed-regexp buffer)
2315         (mixi-post-error 'cannot-find-succeed parent)))))
2316
2317 ;; Message object.
2318 (defconst mixi-message-box-list '(inbox outbox savebox thrash)) ; thrash?
2319
2320 (defmacro mixi-message-box-p (box)
2321   `(memq ,box mixi-message-box-list))
2322
2323 (defun mixi-message-box-name (box)
2324   "Return the name of BOX."
2325   (unless (mixi-message-box-p box)
2326     (signal 'wrong-type-argument (list 'mixi-message-box-p box)))
2327   (symbol-name box))
2328
2329 (defvar mixi-message-cache (make-hash-table :test 'equal))
2330 (defun mixi-make-message (id box &optional owner title time content)
2331   "Return a message object."
2332   (mixi-make-cache (list id box)
2333                    (cons 'mixi-message (vector nil id box owner title time
2334                                                content))
2335                    mixi-message-cache))
2336
2337 (defconst mixi-message-url-regexp
2338   "/view_message\\.pl\\?id=\\([a-z0-9]+\\)&box=\\([a-z]+\\)")
2339
2340 (defun mixi-make-message-from-url (url)
2341   "Return a message object from URL."
2342   (when (string-match mixi-message-url-regexp url)
2343     (let ((id (match-string 1 url))
2344           (box (match-string 2 url)))
2345       (mixi-make-message id box))))
2346
2347 (defmacro mixi-message-p (message)
2348   `(eq (mixi-object-class ,message) 'mixi-message))
2349
2350 (defmacro mixi-message-page (message)
2351   `(concat "/view_message.pl?id=" (mixi-message-id ,message)
2352            "&box=" (mixi-message-box ,message)))
2353
2354 (defconst mixi-message-owner-regexp
2355   "<font COLOR=#996600>\\(º¹½Ð¿Í\\|°¸&nbsp;Àè\\)</font>&nbsp;:&nbsp;<a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.*\\)\\(</a>\\|</td>\\)")
2356 (defconst mixi-message-title-regexp
2357 "<font COLOR=#996600>·ï\\(¡¡\\|&nbsp;\\)̾</font>&nbsp;:&nbsp;\\(.+\\)\n?</td>")
2358 (defconst mixi-message-time-regexp
2359 "<font COLOR=#996600>Æü\\(¡¡\\|&nbsp;\\)ÉÕ</font>&nbsp;:&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\)»þ\\([0-9]+\\)ʬ&nbsp;&nbsp;")
2360 (defconst mixi-message-content-regexp
2361   "<tr><td CLASS=h120>\\(.+\\)</td></tr>")
2362
2363 (defun mixi-realize-message (message)
2364   "Realize a MESSAGE."
2365   (unless (mixi-object-realized-p message)
2366     (with-mixi-retrieve (mixi-message-page message)
2367       (if (string-match mixi-message-owner-regexp buffer)
2368           (mixi-message-set-owner message
2369                                   (mixi-make-friend (match-string 2 buffer)
2370                                                     (match-string 3 buffer)))
2371         (mixi-realization-error 'cannot-find-owner message))
2372       (if (string-match mixi-message-title-regexp buffer)
2373           (mixi-message-set-title message (match-string 2 buffer))
2374         (mixi-realization-error 'cannot-find-title message))
2375       (if (string-match mixi-message-time-regexp buffer)
2376           (mixi-message-set-time
2377            message (encode-time 0 (string-to-number (match-string 6 buffer))
2378                                 (string-to-number (match-string 5 buffer))
2379                                 (string-to-number (match-string 4 buffer))
2380                                 (string-to-number (match-string 3 buffer))
2381                                 (string-to-number (match-string 2 buffer))))
2382         (mixi-realization-error 'cannot-find-time message))
2383       (if (string-match mixi-message-content-regexp buffer)
2384           (mixi-message-set-content message (match-string 1 buffer))
2385         (mixi-realization-error 'cannot-find-content message)))
2386     (mixi-object-touch message)))
2387
2388 (defun mixi-message-id (message)
2389   "Return the id of MESSAGE."
2390   (unless (mixi-message-p message)
2391     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2392   (aref (cdr message) 1))
2393
2394 (defun mixi-message-box (message)
2395   "Return the box of MESSAGE."
2396   (unless (mixi-message-p message)
2397     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2398   (aref (cdr message) 2))
2399
2400 (defun mixi-message-owner (message)
2401   "Return the owner of MESSAGE."
2402   (unless (mixi-message-p message)
2403     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2404   (mixi-realize-message message)
2405   (aref (cdr message) 3))
2406
2407 (defun mixi-message-title (message)
2408   "Return the title of MESSAGE."
2409   (unless (mixi-message-p message)
2410     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2411   (mixi-realize-message message)
2412   (aref (cdr message) 4))
2413
2414 (defun mixi-message-time (message)
2415   "Return the date of MESSAGE."
2416   (unless (mixi-message-p message)
2417     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2418   (mixi-realize-message message)
2419   (aref (cdr message) 5))
2420
2421 (defun mixi-message-content (message)
2422   "Return the content of MESSAGE."
2423   (unless (mixi-message-p message)
2424     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2425   (mixi-realize-message message)
2426   (aref (cdr message) 6))
2427
2428 (defun mixi-message-set-owner (message owner)
2429   "Set the owner of MESSAGE."
2430   (unless (mixi-message-p message)
2431     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2432   (aset (cdr message) 3 owner))
2433
2434 (defun mixi-message-set-title (message title)
2435   "Set the title of MESSAGE."
2436   (unless (mixi-message-p message)
2437     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2438   (aset (cdr message) 4 title))
2439
2440 (defun mixi-message-set-time (message time)
2441   "Set the date of MESSAGE."
2442   (unless (mixi-message-p message)
2443     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2444   (aset (cdr message) 5 time))
2445
2446 (defun mixi-message-set-content (message content)
2447   "Set the content of MESSAGE."
2448   (unless (mixi-message-p message)
2449     (signal 'wrong-type-argument (list 'mixi-message-p message)))
2450   (aset (cdr message) 6 content))
2451
2452 (defmacro mixi-message-list-page (&optional box)
2453   `(concat "/list_message.pl?page=%d"
2454            (when ,box (concat "&box=" ,box))))
2455
2456 (defconst mixi-message-list-regexp
2457   "<td><a HREF=\"view_message\\.pl\\?id=\\(.+\\)&box=\\(.+\\)\">")
2458
2459 (defun mixi-get-messages (&rest box-or-range)
2460   "Get messages of BOX."
2461   (when (> (length box-or-range) 2)
2462     (signal 'wrong-number-of-arguments
2463             (list 'mixi-get-messages (length box-or-range))))
2464   (let ((box (nth 0 box-or-range))
2465         (range (nth 1 box-or-range)))
2466     (when (or (not (mixi-message-box-p box))
2467               (mixi-message-box-p range))
2468       (setq box (nth 1 box-or-range))
2469       (setq range (nth 0 box-or-range)))
2470     (let ((items (mixi-get-matched-items
2471                   (mixi-message-list-page
2472                    (when box (mixi-message-box-name box)))
2473                   mixi-message-list-regexp
2474                   range)))
2475       (mapcar (lambda (item)
2476                 (mixi-make-message (nth 0 item) (nth 1 item)))
2477               items))))
2478
2479 (defmacro mixi-post-message-page (friend)
2480   `(concat "/send_message.pl?id=" (mixi-friend-id friend)))
2481
2482 (defconst mixi-post-message-key-regexp
2483   "<input name=post_key type=hidden value=\\([a-z0-9]+\\)>")
2484
2485 (defconst mixi-post-message-succeed-regexp
2486   "<b>Á÷¿®´°Î»</b>¤·¤Þ¤·¤¿¡£")
2487
2488 (defun mixi-post-message (friend title content)
2489   "Post a message to FRIEND."
2490   (unless (mixi-friend-p friend)
2491     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
2492   (unless (stringp title)
2493     (signal 'wrong-type-argument (list 'stringp title)))
2494   (unless (stringp content)
2495     (signal 'wrong-type-argument (list 'stringp content)))
2496   (let ((fields `(("subject" . ,title)
2497                   ("body" . ,content)
2498                   ("submit" . "main")))
2499         post-key)
2500     (with-mixi-post-form (mixi-post-message-page friend) fields
2501       (if (string-match mixi-post-message-key-regexp buffer)
2502           (setq post-key (match-string 1 buffer))
2503         (mixi-post-error 'cannot-find-key friend)))
2504     (setq fields `(("post_key" . ,post-key)
2505                    ("subject" . ,title)
2506                    ("body" . ,content)
2507                    ("yes" . "¡¡Á÷¡¡¿®¡¡")
2508                    ("submit" . "confirm")))
2509     (with-mixi-post-form (mixi-post-message-page friend) fields
2510       (unless (string-match mixi-post-message-succeed-regexp buffer)
2511         (mixi-post-error 'cannot-find-succeed friend)))))
2512
2513 ;; Introduction object.
2514 (defun mixi-make-introduction (parent owner content)
2515   "Return a introduction object."
2516   (cons 'mixi-introduction (vector parent owner content)))
2517
2518 (defmacro mixi-introduction-p (introduction)
2519   `(eq (mixi-object-class ,introduction) 'mixi-introduction))
2520
2521 (defun mixi-introduction-parent (introduction)
2522   "Return the parent of INTRODUCTION."
2523   (unless (mixi-introduction-p introduction)
2524     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2525   (aref (cdr introduction) 0))
2526
2527 (defun mixi-introduction-owner (introduction)
2528   "Return the owner of INTRODUCTION."
2529   (unless (mixi-introduction-p introduction)
2530     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2531   (aref (cdr introduction) 1))
2532
2533 (defun mixi-introduction-content (introduction)
2534   "Return the content of INTRODUCTION."
2535   (unless (mixi-introduction-p introduction)
2536     (signal 'wrong-type-argument (list 'mixi-introduction-p introduction)))
2537   (aref (cdr introduction) 3))
2538
2539 (defmacro mixi-introduction-list-page (&optional friend)
2540   `(concat "/show_intro.pl?page=%d"
2541            (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
2542
2543 (defconst mixi-introduction-list-regexp
2544   "<tr bgcolor=#FFFFFF>
2545 <td WIDTH=150 background=http://img\\.mixi\\.jp/img/bg_line\\.gif align=\"center\"><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\"><img src=\".+\" border=0><br>
2546 \\(.*\\)</td></a>
2547
2548 <td WIDTH=480>
2549 \\(´Ø·¸¡§.+<br>
2550
2551
2552 \\(\\(.\\|\n<br>\\)+\\)\\|
2553 \\(\\(.\\|\n<br>\\)+\\)\\)
2554
2555
2556
2557
2558 </td>
2559 </tr>")
2560 (defconst mixi-my-introduction-list-regexp
2561   "<tr bgcolor=#FFFFFF>
2562 <td WIDTH=150 background=http://img\\.mixi\\.jp/img/bg_line\\.gif align=\"center\"><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\"><img src=\".+\" border=0><br>
2563 \\(.*\\)</td></a>
2564
2565
2566 <td WIDTH=480>
2567 \\(´Ø·¸¡§.+<br>
2568
2569
2570 \\(\\(.\\|\n<br>\\)+\\)\\|
2571 \\(\\(.\\|\n<br>\\)+\\)\\)
2572
2573
2574 <br>
2575 <a href=\"edit_intro\\.pl\\?id=\\1&type=edit\">¤³¤Îͧ¿Í¤ò¾Ò²ð¤¹¤ë</a>
2576
2577
2578 <BR>
2579 <a href=\"delete_intro\\.pl\\?id=\\1\">ºï½ü</a>
2580
2581 </td>
2582 </tr>")
2583
2584 (defun mixi-get-introductions (&rest friend-or-range)
2585   "Get introductions of FRIEND."
2586   (when (> (length friend-or-range) 2)
2587     (signal 'wrong-number-of-arguments
2588             (list 'mixi-get-introduction (length friend-or-range))))
2589   (let ((friend (nth 0 friend-or-range))
2590         (range (nth 1 friend-or-range)))
2591     (when (or (not (mixi-friend-p friend)) (mixi-friend-p range))
2592       (setq friend (nth 1 friend-or-range))
2593       (setq range (nth 0 friend-or-range)))
2594     (unless (or (null friend) (mixi-friend-p friend))
2595       (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
2596     (let* ((regexp (if friend mixi-introduction-list-regexp
2597                      mixi-my-introduction-list-regexp))
2598            (items (mixi-get-matched-items (mixi-introduction-list-page friend)
2599                                           regexp
2600                                           range)))
2601       (mapcar (lambda (item)
2602                 (mixi-make-introduction (or friend (mixi-make-me))
2603                                         (mixi-make-friend (nth 0 item)
2604                                                           (nth 1 item))
2605                                         (nth 2 item)))
2606               items))))
2607
2608 ;; News object.
2609 (defvar mixi-news-cache (make-hash-table :test 'equal))
2610 (defun mixi-make-news (media-id id &optional media time title content)
2611   "Return a news object."
2612   (mixi-make-cache (list media-id id)
2613                    (cons 'mixi-news (vector nil media-id id media time title
2614                                             content))
2615                    mixi-news-cache))
2616
2617 (defconst mixi-news-url-regexp
2618   "/view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)")
2619
2620 (defun mixi-make-news-from-url (url)
2621   "Return a news object from URL."
2622   (when (string-match mixi-news-url-regexp url)
2623     (let ((id (match-string 1 url))
2624           (media-id (match-string 2 url)))
2625       (mixi-make-news media-id id))))
2626
2627 (defmacro mixi-news-p (news)
2628   `(eq (mixi-object-class ,news) 'mixi-news))
2629
2630 (defmacro mixi-news-page (news)
2631   `(concat "http://news.mixi.jp/view_news.pl?id=" (mixi-news-id ,news)
2632            "&media_id=" (mixi-news-media-id ,news)))
2633
2634 (defconst mixi-news-title-regexp
2635   "<td HEIGHT=\"46\" STYLE=\"font-weight: bold;font-size: 14px;\" CLASS=\"h130\">\\(.+\\)</td>")
2636 (defconst mixi-news-media-time-regexp
2637   "<td COLSPAN=\"2\" ALIGN=\"right\">(\\(.+\\)&nbsp;-&nbsp;\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\))</td></tr>")
2638 (defconst mixi-news-content-regexp
2639   "<td CLASS=\"h150\">
2640
2641 \\(.+\\)
2642
2643 ?
2644
2645 \\(</td>\\|<br>\\)")
2646
2647 (defun mixi-realize-news (news)
2648   "Realize a NEWS."
2649   ;; FIXME: Check a expiration of cache?
2650   (unless (mixi-object-realized-p news)
2651     (with-mixi-retrieve (mixi-news-page news)
2652       (if (string-match mixi-news-title-regexp buffer)
2653           (mixi-news-set-title news (match-string 1 buffer))
2654         (mixi-realization-error 'cannot-find-title news))
2655       (if (string-match mixi-news-media-time-regexp buffer)
2656           (progn
2657             (mixi-news-set-media news (match-string 1 buffer))
2658             (let ((year (nth 5 (decode-time (current-time))))
2659                   (month (nth 4 (decode-time (current-time))))
2660                   (month-of-item (string-to-number (match-string 2 buffer))))
2661               (when (> month-of-item month)
2662                 (decf year))
2663               (mixi-news-set-time
2664                news (encode-time 0 (string-to-number (match-string 5 buffer))
2665                                  (string-to-number (match-string 4 buffer))
2666                                  (string-to-number (match-string 3 buffer))
2667                                  month year))))
2668         (mixi-realization-error 'cannot-find-media-time news))
2669       (if (string-match mixi-news-content-regexp buffer)
2670           (mixi-news-set-content news (match-string 1 buffer))
2671         (mixi-realization-error 'cannot-find-content news)))
2672     (mixi-object-touch news)))
2673
2674 (defun mixi-news-media-id (news)
2675   "Return the media-id of NEWS."
2676   (unless (mixi-news-p news)
2677     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2678   (aref (cdr news) 1))
2679
2680 (defun mixi-news-id (news)
2681   "Return the id of NEWS."
2682   (unless (mixi-news-p news)
2683     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2684   (aref (cdr news) 2))
2685
2686 (defun mixi-news-media (news)
2687   "Return the media of NEWS."
2688   (unless (mixi-news-p news)
2689     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2690   (unless (aref (cdr news) 3)
2691     (mixi-realize-news news))
2692   (aref (cdr news) 3))
2693
2694 (defun mixi-news-time (news)
2695   "Return the time of NEWS."
2696   (unless (mixi-news-p news)
2697     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2698   (unless (aref (cdr news) 4)
2699     (mixi-realize-news news))
2700   (aref (cdr news) 4))
2701
2702 (defun mixi-news-title (news)
2703   "Return the title of NEWS."
2704   (unless (mixi-news-p news)
2705     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2706   (unless (aref (cdr news) 5)
2707     (mixi-realize-news news))
2708   (aref (cdr news) 5))
2709
2710 (defun mixi-news-content (news)
2711   "Return the content of NEWS."
2712   (unless (mixi-news-p news)
2713     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2714   (mixi-realize-news news)
2715   (aref (cdr news) 6))
2716
2717 (defun mixi-news-set-media (news media)
2718   "Set the media of NEWS."
2719   (unless (mixi-news-p news)
2720     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2721   (aset (cdr news) 3 media))
2722
2723 (defun mixi-news-set-time (news time)
2724   "Set the time of NEWS."
2725   (unless (mixi-news-p news)
2726     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2727   (aset (cdr news) 4 time))
2728
2729 (defun mixi-news-set-title (news title)
2730   "Set the title of NEWS."
2731   (unless (mixi-news-p news)
2732     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2733   (aset (cdr news) 5 title))
2734
2735 (defun mixi-news-set-content (news content)
2736   "Set the content of NEWS."
2737   (unless (mixi-news-p news)
2738     (signal 'wrong-type-argument (list 'mixi-news-p news)))
2739   (aset (cdr news) 6 content))
2740
2741 (defconst mixi-news-category-list '(domestic politics economy area abroad
2742                                              sports entertainment IT))
2743
2744 (defmacro mixi-news-category-p (category)
2745   `(memq ,category mixi-news-category-list))
2746
2747 (defun mixi-news-category-id (category)
2748   "Return the id of CATEGORY."
2749   (unless (mixi-news-category-p category)
2750     (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
2751   (number-to-string
2752    (1+ (- (length mixi-news-category-list)
2753           (length (memq category mixi-news-category-list))))))
2754
2755 (defconst mixi-news-sort-list '(newest pickup))
2756
2757 (defmacro mixi-news-sort-p (sort)
2758   `(memq ,sort mixi-news-sort-list))
2759
2760 (defun mixi-news-sort-id (sort)
2761   "Return the id of SORT."
2762   (unless (mixi-news-sort-p sort)
2763     (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
2764   (number-to-string
2765    (- (length mixi-news-sort-list)
2766       (length (memq sort mixi-news-sort-list)))))
2767
2768 (defmacro mixi-news-list-page (category sort)
2769   `(concat "http://news.mixi.jp/list_news_category.pl?page=%d"
2770            "&sort=" (mixi-news-sort-id ,sort)
2771            "&id=" (mixi-news-category-id ,category)
2772            "&type=bn"))
2773
2774 (defconst mixi-news-list-regexp
2775   "<tr bgcolor=\"\\(#FCF5EB\\|#FFFFFF\\)\">
2776 <td WIDTH=\"1%\" valign=top CLASS=\"h120\">¡¦</td>
2777 <td WIDTH=\"97%\" CLASS=\"h120\"><A HREF=\"view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)\"class=\"new_link\">\\(.+\\)</A>
2778 \\(<IMG SRC=\"http://img\\.mixi\\.jp/img/news_camera3\\.gif\" WIDTH=\"11\" HEIGHT=\"12\">\\|\\)
2779
2780 </td>
2781 <td WIDTH=\"1%\" nowrap CLASS=\"f08\"><A HREF=\"list_news_media\\.pl\\?id=[0-9]+\">\\(.+\\)</A></td>
2782 <td WIDTH=\"1%\" nowrap CLASS=\"f08\">\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td></tr>")
2783
2784 (defun mixi-get-news (category sort &optional range)
2785   "Get news of CATEGORY and SORT."
2786   (unless (mixi-news-category-p category)
2787     (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
2788   (unless (mixi-news-sort-p sort)
2789     (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
2790   (let ((items (mixi-get-matched-items (mixi-news-list-page category sort)
2791                                        mixi-news-list-regexp
2792                                        range))
2793         (year (nth 5 (decode-time (current-time))))
2794         (month (nth 4 (decode-time (current-time)))))
2795     (mapcar (lambda (item)
2796               (let ((month-of-item (string-to-number (nth 6 item))))
2797                 (when (> month-of-item month)
2798                   (decf year))
2799                 (setq month month-of-item)
2800                 (mixi-make-news (nth 2 item) (nth 1 item) (nth 5 item)
2801                                 (encode-time
2802                                  0 (string-to-number (nth 9 item))
2803                                  (string-to-number (nth 8 item))
2804                                  (string-to-number (nth 7 item))
2805                                  month year)
2806                                 (nth 3 item))))
2807             items)))
2808
2809 (provide 'mixi)
2810
2811 ;;; mixi.el ends here