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