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