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