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