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