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