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