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