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