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