* mixi.el (mixi-message-cache): New variable.
[elisp/mixi.git] / mixi.el
diff --git a/mixi.el b/mixi.el
index 62900a9..dfe2348 100644 (file)
--- a/mixi.el
+++ b/mixi.el
@@ -39,9 +39,9 @@
 
 ;; Example:
 ;;
-;; Display only the first page of new diaries like a mail format.
+;; Display newest 3 diaries like a mail format.
 ;;
-;; (let ((mixi-new-diary-max-pages 1)
+;; (let ((max-numbers 3)
 ;;       (buffer (generate-new-buffer "*temp*"))
 ;;       (format "%Y/%m/%d %H:%M"))
 ;;   (pop-to-buffer buffer)
 ;;                 "Subject: " subject "\n"
 ;;                 "Date: " date "\n\n"
 ;;                 body "\n\n")))
-;;     (mixi-get-new-diaries))
+;;     (mixi-get-new-diaries max-numbers))
 ;;   (set-buffer-modified-p nil)
 ;;   (setq buffer-read-only t)
 ;;   (goto-char (point-min)))
 ;;
-;; Display only the first page of new diaries including all comments like a
-;; mail format.  Comments are displayed like a reply mail.
+;; Display newest 3 diaries including newest 3 comments like a mail format.
+;; Comments are displayed like a reply mail.
 ;;
-;; (let ((mixi-new-diary-max-pages 1)
+;; (let ((max-numbers 3)
 ;;       (buffer (generate-new-buffer "*temp*"))
 ;;       (format "%Y/%m/%d %H:%M"))
 ;;   (pop-to-buffer buffer)
 ;;                           "Subject: " subject "\n"
 ;;                           "Date: " date "\n\n"
 ;;                           body "\n\n")))
-;;               (mixi-get-comments diary))))
-;;     (mixi-get-new-diaries))
+;;               (mixi-get-comments diary max-numbers))))
+;;     (mixi-get-new-diaries max-numbers))
 ;;   (set-buffer-modified-p nil)
 ;;   (setq buffer-read-only t)
 ;;   (goto-char (point-min)))
 
 ;;; Code:
 
-(require 'w3m)
+(condition-case nil
+    (require 'url)
+  (error))
+
+(condition-case nil
+    (require 'w3m)
+  (error))
+
 (eval-when-compile (require 'cl))
 
 (defgroup mixi nil
   :type 'coding-system
   :group 'mixi)
 
+(defcustom mixi-retrieve-function
+  (if (fboundp 'url-retrieve-synchronously)
+      'mixi-w3-retrieve 'mixi-w3m-retrieve)
+  "*The function for retrieving."
+  :type '(choice (const :tag "Using w3" mixi-w3-retrieve)
+                (const :tag "Using w3m" mixi-w3m-retrieve)
+                (const :tag "Using curl" mixi-curl-retrieve)
+                (function :format "Other function: %v\n" :size 0))
+  :group 'mixi)
+
+(defcustom mixi-curl-program "curl"
+  "*The program name of `curl'."
+  :type 'file
+  :group 'mixi)
+
+(defcustom mixi-curl-cookie-file (expand-file-name "~/.mixi-cookies.txt")
+  "*The location of cookie file created by `curl'."
+  :type 'file
+  :group 'mixi)
+
 (defcustom mixi-default-email nil
   "*Default E-mail address that is used to login automatically."
   :type '(choice (string :tag "E-mail address")
   :type 'boolean
   :group 'mixi)
 
-(defcustom mixi-continuously-access-interval 3.0
+(defcustom mixi-continuously-access-interval 4.0
   "*Time interval between each mixi access.
 Increase this value when unexpected error frequently occurs."
   :type 'number
@@ -155,13 +182,6 @@ Increase this value when unexpected error frequently occurs."
   :type 'directory
   :group 'mixi)
 
-(defcustom mixi-verbose t
-  "*Flag controls whether `mixi' should be verbose.
-If it is non-ni, the `w3m-verbose' variable will be bound to nil
-while `mixi' is waiting for a server's response."
-  :type 'boolean
-  :group 'mixi)
-
 (defvar mixi-me nil)
 
 ;; Utilities.
@@ -180,33 +200,102 @@ while `mixi' is waiting for a server's response."
 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
 
-(defun mixi-retrieve (url &optional post-data)
-  "Retrieve the URL and return getted strings."
+(defun mixi-retrieve-1 (buffer url &optional post-data)
+  (when (string-match mixi-message-adult-contents buffer)
+    (if mixi-accept-adult-contents
+       (setq buffer (funcall mixi-retrieve-function url "submit=agree"))
+      (setq buffer (funcall mixi-retrieve-function (concat url "?")))))
+  (when (string-match mixi-warning-continuously-accessing buffer)
+    (error (mixi-message "Continuously accessing")))
+  (if (not (string-match mixi-message-continuously-accessing buffer))
+      buffer
+    (message (mixi-message "Waiting for continuously accessing..."))
+    (sit-for mixi-continuously-access-interval)
+    (funcall mixi-retrieve-function url post-data)))
+
+(defun mixi-w3-retrieve (url &optional post-data)
+  "Retrieve the URL and return gotten strings."
+  (if post-data
+      (progn
+       (setq url-request-method "POST")
+       (setq url-request-data post-data))
+    (setq url-request-method "GET")
+    (setq url-request-data nil))
+  (let* ((url (url-expand-file-name url mixi-url))
+        (buffer (url-retrieve-synchronously url))
+        ret)
+    (unless (bufferp buffer)
+      (error (mixi-message "Cannot retrieve")))
+    (with-current-buffer buffer
+      (goto-char (point-min))
+      (if (re-search-forward "HTTP/[0-9.]+ 302 Moved" nil t)
+         (if (re-search-forward
+              (concat "Location: " mixi-url "\\(.+\\)") nil t)
+             (setq ret (mixi-w3-retrieve (match-string 1) post-data))
+           (setq ret (mixi-w3-retrieve "/home.pl" post-data)))
+       (unless (re-search-forward "HTTP/[0-9.]+ 200 OK" nil t)
+         (error (mixi-message "Cannot retrieve")))
+       (search-forward "\n\n")
+       (setq ret (mm-decode-coding-string
+                  (buffer-substring-no-properties (point) (point-max))
+                  mixi-coding-system))
+       (kill-buffer buffer)
+       (setq ret (mixi-retrieve-1 ret url post-data))))
+    ret))
+
+(defun mixi-w3m-retrieve (url &optional post-data)
+  "Retrieve the URL and return gotten strings."
   (let ((url (w3m-expand-url url mixi-url)))
     (with-temp-buffer
-      (let ((w3m-verbose (if mixi-verbose nil w3m-verbose)))
-       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
-           (error (mixi-message "Cannot retrieve"))
-         (w3m-decode-buffer url)
-         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
-           (when (string-match mixi-message-adult-contents ret)
-             (if mixi-accept-adult-contents
-                 (setq ret (mixi-retrieve url "submit=agree"))
-               (setq ret (mixi-retrieve (concat url "?")))))
-           (when (string-match mixi-warning-continuously-accessing ret)
-             (error (mixi-message "Continuously accessing")))
-           (if (not (string-match mixi-message-continuously-accessing ret))
-               ret
-             (message (mixi-message "Waiting for continuously accessing..."))
-             (sit-for mixi-continuously-access-interval)
-             (mixi-retrieve url post-data))))))))
+      (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
+         (error (mixi-message "Cannot retrieve"))
+       (w3m-decode-buffer url)
+       (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
+         (mixi-retrieve-1 ret url post-data))))))
+
+(defun mixi-curl-retrieve (url &optional post-data)
+  "Retrieve the URL and return gotten strings."
+  (with-temp-buffer
+    (if (fboundp 'set-buffer-multibyte)
+       (set-buffer-multibyte nil))
+    (let ((orig-mode (default-file-modes))
+         (coding-system-for-read 'binary)
+         (coding-system-for-write 'binary)
+         process ret)
+      (unwind-protect
+         (progn
+           (set-default-file-modes 448)
+           (setq process
+                 (apply #'start-process "curl" (current-buffer)
+                        mixi-curl-program
+                        (append (if post-data '("-d" "@-"))
+                                (list "-i" "-L" "-s"
+                                      "-b" mixi-curl-cookie-file
+                                      "-c" mixi-curl-cookie-file
+                                      (concat mixi-url url)))))
+           (set-process-sentinel process #'ignore))
+       (set-default-file-modes orig-mode))
+      (when post-data
+       (process-send-string process (concat post-data "\n"))
+       (process-send-eof process))
+      (while (eq (process-status process) 'run)
+       (accept-process-output process 1))
+      (goto-char (point-min))
+      (while (looking-at "HTTP/[0-9]+\\.[0-9]+ [13][0-9][0-9]")
+       (delete-region (point) (re-search-forward "\r?\n\r?\n")))
+      (unless (looking-at "HTTP/[0-9]+\\.[0-9]+ 200")
+       (error (mixi-message "Cannot retrieve")))
+      (delete-region (point) (re-search-forward "\r?\n\r?\n"))
+      (setq ret (decode-coding-string (buffer-string) mixi-coding-system))
+      (mixi-retrieve-1 ret url post-data))))
 
 (defconst mixi-my-id-regexp
   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
 
 (defun mixi-login (&optional email password)
   "Login to mixi."
-  (unless w3m-use-cookies
+  (when (and (eq mixi-retrieve-function 'mixi-w3m-retrieve)
+            (not w3m-use-cookies))
     (error
      (mixi-message
       "Require to accept cookies.  Please set `w3m-use-cookies' to t.")))
@@ -214,58 +303,58 @@ while `mixi' is waiting for a server's response."
                   (read-from-minibuffer (mixi-message "Login Email: "))))
        (password (or password mixi-default-password
                      (read-passwd (mixi-message "Login Password: ")))))
-    (let ((buffer (mixi-retrieve "/login.pl"
-                                (concat "email=" email
-                                        "&password=" password
-                                        "&next_url=/home.pl"
-                                        "&sticky=on"))))
+    (let ((buffer (funcall mixi-retrieve-function "/login.pl"
+                          (concat "email=" email
+                                  "&password=" password
+                                  "&next_url=/home.pl"
+                                  "&sticky=on"))))
       (unless (string-match "url=/check\\.pl\\?n=" buffer)
        (error (mixi-message "Cannot login")))
-      (setq buffer (mixi-retrieve "/check.pl?n=home.pl"))
+      (setq buffer (funcall mixi-retrieve-function "/check.pl?n=home.pl"))
       (if (string-match mixi-my-id-regexp buffer)
-         (setq mixi-me (mixi-make-friend
-                        (string-to-number (match-string 1 buffer))))
+         (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
        (error (mixi-message "Cannot login"))))))
 
 (defun mixi-logout ()
-  (mixi-retrieve "/logout.pl"))
+  (funcall mixi-retrieve-function "/logout.pl"))
 
 (defmacro with-mixi-retrieve (url &rest body)
   `(let (buffer)
      (when ,url
-       (setq buffer (mixi-retrieve ,url))
+       (setq buffer (funcall mixi-retrieve-function ,url))
        (when (string-match "login.pl" buffer)
         (mixi-login)
-        (setq buffer (mixi-retrieve ,url))))
+        (setq buffer (funcall mixi-retrieve-function ,url))))
      ,@body))
 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
+(put 'with-mixi-retrieve 'edebug-form-spec '(form body))
 
-(defun mixi-get-matched-items (url max-pages regexp)
+(defun mixi-get-matched-items (url max-numbers regexp)
   "Get matched items to REGEXP in URL."
   (let ((page 1)
        ids)
     (catch 'end
-      (while (or (null max-pages) (<= page max-pages))
+      (while (or (null max-numbers) (< (length ids) max-numbers))
        (with-mixi-retrieve (format url page)
          (let ((pos 0))
-           (while (string-match regexp buffer pos)
+           (while (and (string-match regexp buffer pos)
+                       (or (null max-numbers) (< (length ids) max-numbers)))
              (let ((num 1)
                    list)
                (while (match-string num buffer)
-                 (let ((string (match-string num buffer)))
-                   (save-match-data
-                     (when (string-match "^[0-9]+$" string)
-                       (setq string (string-to-number string))))
-                   (setq list (cons string list)))
+                 (setq list (cons (match-string num buffer) list))
                  (incf num))
-             (setq ids (cons (reverse list) ids))
-             (setq pos (match-end (1- num)))))
+               (when (member (reverse list) ids)
+                 (throw 'end ids))
+               (setq ids (cons (reverse list) ids))
+               (setq pos (match-end (1- num)))))
            (when (eq pos 0)
              (throw 'end ids))))
        (incf page)))
     ;; FIXME: Sort? Now order by newest.
     (reverse ids)))
 
+;; stolen (and modified) from shimbun.el
 (defun mixi-remove-markup (string)
   "Remove markups from STRING."
   (with-temp-buffer
@@ -282,10 +371,26 @@ while `mixi' is waiting for a server's response."
       (goto-char (point-min))
       (while (re-search-forward "\r" nil t)
        (replace-match "\n" t t)))
-    (w3m-decode-entities)
     (buffer-string)))
 
 ;; Cache.
+;; stolen from time-date.el
+(defun mixi-time-less-p (t1 t2)
+  "Say whether time value T1 is less than time value T2."
+  (or (< (car t1) (car t2))
+      (and (= (car t1) (car t2))
+          (< (nth 1 t1) (nth 1 t2)))))
+
+(defun mixi-time-add (t1 t2)
+  "Add two time values.  One should represent a time difference."
+  (let ((low (+ (cdr t1) (cdr t2))))
+    (cons (+ (car t1) (car t2) (lsh low -16)) low)))
+
+;; stolen from time-date.el
+(defun mixi-seconds-to-time (seconds)
+  "Convert SECONDS (a floating point number) to a time value."
+  (cons (floor seconds 65536)
+       (floor (mod seconds 65536))))
 
 (defun mixi-cache-expired-p (object)
   "Whether a cache of OBJECT is expired."
@@ -293,9 +398,9 @@ while `mixi' is waiting for a server's response."
   (let ((timestamp (aref (cdr object) 0)))
     (unless (or (null mixi-cache-expires)
                 (null timestamp))
-      (time-less-p (time-add timestamp
-                            (seconds-to-time mixi-cache-expires))
-                  (current-time)))))
+      (mixi-time-less-p
+       (mixi-time-add timestamp (mixi-seconds-to-time mixi-cache-expires))
+       (current-time)))))
 
 (defun mixi-make-cache (key value table)
   "Make a cache object and return it."
@@ -341,8 +446,7 @@ while `mixi' is waiting for a server's response."
   (unless mixi-me
     (with-mixi-retrieve "/home.pl"
       (if (string-match mixi-my-id-regexp buffer)
-         (setq mixi-me
-               (mixi-make-friend (string-to-number (match-string 1 buffer))))
+         (setq mixi-me (mixi-make-friend (match-string 1 buffer)))
        (signal 'error (list 'who-am-i)))))
   mixi-me)
 
@@ -350,7 +454,7 @@ while `mixi' is waiting for a server's response."
   `(eq (mixi-object-class ,friend) 'mixi-friend))
 
 (defmacro mixi-friend-page (friend)
-  `(concat "/show_friend.pl?id=" (number-to-string (mixi-friend-id ,friend))))
+  `(concat "/show_friend.pl?id=" (mixi-friend-id ,friend)))
 
 (defconst mixi-friend-nick-regexp
   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
@@ -598,32 +702,37 @@ while `mixi' is waiting for a server's response."
 
 (defmacro mixi-friend-list-page (&optional friend)
   `(concat "/list_friend.pl?page=%d"
-          (when ,friend (concat "&id=" (number-to-string
-                                        (mixi-friend-id ,friend))))))
+          (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
 
 (defconst mixi-friend-list-id-regexp
   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
 (defconst mixi-friend-list-nick-regexp
   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
 
-(defvar mixi-friend-max-pages 10)
-(defun mixi-get-friends (&optional friend)
+(defun mixi-get-friends (&rest args)
   "Get friends of FRIEND."
-  (unless (or (null friend) (mixi-friend-p friend))
-    (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
-  (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
-                                    mixi-friend-max-pages
-                                    mixi-friend-list-id-regexp))
-       (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
-                                      mixi-friend-max-pages
-                                      mixi-friend-list-nick-regexp)))
-    (let ((index 0)
-         ret)
-      (while (< index (length ids))
-       (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
-                                         (nth 0 (nth index nicks))) ret))
-       (incf index))
-      (reverse ret))))
+  (when (> (length args) 2)
+    (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
+  (let ((friend (nth 0 args))
+       (max-numbers (nth 1 args)))
+    (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
+      (setq friend (nth 1 args))
+      (setq max-numbers (nth 0 args)))
+    (unless (or (null friend) (mixi-friend-p friend))
+      (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
+    (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
+                                      max-numbers
+                                      mixi-friend-list-id-regexp))
+         (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
+                                        max-numbers
+                                        mixi-friend-list-nick-regexp)))
+      (let ((index 0)
+           ret)
+       (while (< index (length ids))
+         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
+                                           (nth 0 (nth index nicks))) ret))
+         (incf index))
+       (reverse ret)))))
 
 ;; Favorite.
 (defmacro mixi-favorite-list-page ()
@@ -635,14 +744,13 @@ while `mixi' is waiting for a server's response."
   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
 
-(defvar mixi-favorite-max-pages nil)
-(defun mixi-get-favorites ()
+(defun mixi-get-favorites (&optional max-numbers)
   "Get favorites."
   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
-                                    mixi-favorite-max-pages
+                                    max-numbers
                                     mixi-favorite-list-id-regexp))
        (nicks (mixi-get-matched-items (mixi-favorite-list-page)
-                                      mixi-favorite-max-pages
+                                      max-numbers
                                       mixi-favorite-list-nick-regexp)))
     (let ((index 0)
          ret)
@@ -678,17 +786,19 @@ while `mixi' is waiting for a server's response."
 (defconst mixi-log-list-regexp
   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a><br>")
 
-(defvar mixi-log-max-pages 1)
-(defun mixi-get-logs ()
+(defun mixi-get-logs (&optional max-numbers)
   "Get logs."
   (let ((items (mixi-get-matched-items (mixi-log-list-page)
-                                      mixi-log-max-pages
+                                      max-numbers
                                       mixi-log-list-regexp)))
     (mapcar (lambda (item)
              (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
-                            (encode-time 0 (nth 4 item) (nth 3 item)
-                                         (nth 2 item) (nth 1 item)
-                                         (nth 0 item))))
+                            (encode-time 0
+                                         (string-to-number (nth 4 item))
+                                         (string-to-number (nth 3 item))
+                                         (string-to-number (nth 2 item))
+                                         (string-to-number (nth 1 item))
+                                         (string-to-number (nth 0 item)))))
            items)))
 
 ;; Diary object.
@@ -704,9 +814,8 @@ while `mixi' is waiting for a server's response."
   `(eq (mixi-object-class ,diary) 'mixi-diary))
 
 (defmacro mixi-diary-page (diary)
-  `(concat "/view_diary.pl?id=" (number-to-string (mixi-diary-id ,diary))
-          "&owner_id=" (number-to-string (mixi-friend-id
-                                          (mixi-diary-owner ,diary)))))
+  `(concat "/view_diary.pl?id=" (mixi-diary-id ,diary)
+          "&owner_id=" (mixi-friend-id (mixi-diary-owner ,diary))))
 
 ;; FIXME: Remove `¤µ¤ó'.
 (defconst mixi-diary-owner-nick-regexp
@@ -809,23 +918,28 @@ while `mixi' is waiting for a server's response."
 
 (defmacro mixi-diary-list-page (&optional friend)
   `(concat "/list_diary.pl?page=%d"
-          (when ,friend (concat "&id=" (number-to-string
-                                        (mixi-friend-id ,friend))))))
+          (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
 
 (defconst mixi-diary-list-regexp
   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
 
-(defvar mixi-diary-max-pages nil)
-(defun mixi-get-diaries (&optional friend)
+(defun mixi-get-diaries (&rest args)
   "Get diaries of FRIEND."
-  (unless (or (null friend) (mixi-friend-p friend))
-    (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
-  (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
-                                      mixi-diary-max-pages
-                                      mixi-diary-list-regexp)))
-    (mapcar (lambda (item)
-             (mixi-make-diary friend (nth 0 item)))
-           items)))
+  (when (> (length args) 2)
+    (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
+  (let ((friend (nth 0 args))
+       (max-numbers (nth 1 args)))
+    (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
+      (setq friend (nth 1 args))
+      (setq max-numbers (nth 0 args)))
+    (unless (or (null friend) (mixi-friend-p friend))
+      (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
+    (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
+                                        max-numbers
+                                        mixi-diary-list-regexp)))
+      (mapcar (lambda (item)
+               (mixi-make-diary friend (nth 0 item)))
+             items))))
 
 (defmacro mixi-new-diary-list-page ()
   `(concat "/new_friend_diary.pl?page=%d"))
@@ -833,11 +947,10 @@ while `mixi' is waiting for a server's response."
 (defconst mixi-new-diary-list-regexp
   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
 
-(defvar mixi-new-diary-max-pages nil)
-(defun mixi-get-new-diaries ()
+(defun mixi-get-new-diaries (&optional max-numbers)
   "Get new diaries."
   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
-                                      mixi-new-diary-max-pages
+                                      max-numbers
                                       mixi-new-diary-list-regexp)))
     (mapcar (lambda (item)
              (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
@@ -855,8 +968,7 @@ while `mixi' is waiting for a server's response."
   `(eq (mixi-object-class ,community) 'mixi-community))
 
 (defmacro mixi-community-page (community)
-  `(concat "/view_community.pl?id=" (number-to-string
-                                    (mixi-community-id ,community))))
+  `(concat "/view_community.pl?id=" (mixi-community-id ,community)))
 
 (defconst mixi-community-nodata-regexp
   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
@@ -901,9 +1013,8 @@ while `mixi' is waiting for a server's response."
            (if (string= (match-string 1 buffer) "home.pl")
                (mixi-community-set-owner community (mixi-make-me))
              (mixi-community-set-owner
-              community (mixi-make-friend
-                         (string-to-number (match-string 2 buffer))
-                         (match-string 3 buffer))))
+              community (mixi-make-friend (match-string 2 buffer)
+                                          (match-string 3 buffer))))
          (signal 'error (list 'cannot-find-owner community)))
        (if (string-match mixi-community-category-regexp buffer)
            (mixi-community-set-category community (match-string 1 buffer))
@@ -1051,32 +1162,37 @@ while `mixi' is waiting for a server's response."
 
 (defmacro mixi-community-list-page (&optional friend)
   `(concat "/list_community.pl?page=%d"
-          (when ,friend (concat "&id=" (number-to-string
-                                        (mixi-friend-id ,friend))))))
+          (when ,friend (concat "&id=" (mixi-friend-id ,friend)))))
 
 (defconst mixi-community-list-id-regexp
   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
 (defconst mixi-community-list-name-regexp
   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
 
-(defvar mixi-community-max-pages nil)
-(defun mixi-get-communities (&optional friend)
+(defun mixi-get-communities (&rest args)
   "Get communities of FRIEND."
-  (unless (or (null friend) (mixi-friend-p friend))
-    (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
-  (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
-                                    mixi-community-max-pages
-                                    mixi-community-list-id-regexp))
-       (names (mixi-get-matched-items (mixi-community-list-page friend)
-                                    mixi-community-max-pages
-                                    mixi-community-list-name-regexp)))
-    (let ((index 0)
-         ret)
-      (while (< index (length ids))
-       (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
-                                            (nth 0 (nth index names))) ret))
-       (incf index))
-      (reverse ret))))
+  (when (> (length args) 2)
+    (signal 'wrong-number-of-arguments (list 'mixi-get-friends (length args))))
+  (let ((friend (nth 0 args))
+       (max-numbers (nth 1 args)))
+    (when (or (not (mixi-friend-p friend)) (mixi-friend-p max-numbers))
+      (setq friend (nth 1 args))
+      (setq max-numbers (nth 0 args)))
+    (unless (or (null friend) (mixi-friend-p friend))
+      (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
+    (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
+                                      max-numbers
+                                      mixi-community-list-id-regexp))
+         (names (mixi-get-matched-items (mixi-community-list-page friend)
+                                        max-numbers
+                                        mixi-community-list-name-regexp)))
+      (let ((index 0)
+           ret)
+       (while (< index (length ids))
+         (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
+                                              (nth 0 (nth index names))) ret))
+         (incf index))
+       (reverse ret)))))
 
 ;; Topic object.
 (defvar mixi-topic-cache (make-hash-table :test 'equal))
@@ -1090,9 +1206,8 @@ while `mixi' is waiting for a server's response."
   `(eq (mixi-object-class ,topic) 'mixi-topic))
 
 (defmacro mixi-topic-page (topic)
-  `(concat "/view_bbs.pl?id=" (number-to-string (mixi-topic-id ,topic))
-          "&comm_id=" (number-to-string
-                       (mixi-community-id (mixi-topic-community ,topic)))))
+  `(concat "/view_bbs.pl?id=" (mixi-topic-id ,topic)
+          "&comm_id=" (mixi-community-id (mixi-topic-community ,topic))))
 
 (defconst mixi-topic-time-regexp
   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
@@ -1122,9 +1237,8 @@ while `mixi' is waiting for a server's response."
        (signal 'error (list 'cannot-find-title topic)))
       (if (string-match mixi-topic-owner-regexp buffer)
          (mixi-topic-set-owner topic
-                               (mixi-make-friend
-                                (string-to-number (match-string 1 buffer))
-                                (match-string 2 buffer)))
+                               (mixi-make-friend (match-string 1 buffer)
+                                                 (match-string 2 buffer)))
        (signal 'error (list 'cannot-find-owner topic)))
       (if (string-match mixi-topic-content-regexp buffer)
          (mixi-topic-set-content topic (mixi-remove-markup
@@ -1212,18 +1326,17 @@ while `mixi' is waiting for a server's response."
 
 (defmacro mixi-topic-list-page (community)
   `(concat "/list_bbs.pl?page=%d"
-          "&id=" (number-to-string (mixi-community-id ,community))))
+          "&id=" (mixi-community-id ,community)))
 
 (defconst mixi-topic-list-regexp
   "<a href=view_bbs\\.pl\\?id=\\([0-9]+\\)")
 
-(defvar mixi-topic-max-pages nil)
-(defun mixi-get-topics (community)
+(defun mixi-get-topics (community &optional max-numbers)
   "Get topics of COMMUNITY."
   (unless (mixi-community-p community)
     (signal 'wrong-type-argument (list 'mixi-community-p community)))
   (let ((items (mixi-get-matched-items (mixi-topic-list-page community)
-                                      mixi-topic-max-pages
+                                      max-numbers
                                       mixi-topic-list-regexp)))
     (mapcar (lambda (item)
              (mixi-make-topic community (nth 0 item)))
@@ -1235,11 +1348,10 @@ while `mixi' is waiting for a server's response."
 (defconst mixi-new-topic-list-regexp
   "<a href=\"view_bbs\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
 
-(defvar mixi-new-topic-max-pages nil)
-(defun mixi-get-new-topics ()
+(defun mixi-get-new-topics (&optional max-numbers)
   "Get new topics."
   (let ((items (mixi-get-matched-items (mixi-new-topic-list-page)
-                                      mixi-new-topic-max-pages
+                                      max-numbers
                                       mixi-new-topic-list-regexp)))
     (mapcar (lambda (item)
              (mixi-make-topic (mixi-make-community (nth 1 item))
@@ -1279,10 +1391,9 @@ while `mixi' is waiting for a server's response."
   (aref (cdr comment) 3))
 
 (defun mixi-diary-comment-list-page (diary)
-  (concat "/view_diary.pl?page=all"
-         "&id=" (number-to-string (mixi-diary-id diary))
-         "&owner_id=" (number-to-string
-                       (mixi-friend-id (mixi-diary-owner diary)))))
+  (concat "/view_diary.pl?page=%d"
+         "&id=" (mixi-diary-id diary)
+         "&owner_id=" (mixi-friend-id (mixi-diary-owner diary))))
 
 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
 (defconst mixi-diary-comment-list-regexp
@@ -1312,10 +1423,9 @@ while `mixi' is waiting for a server's response."
 </td></tr></table>")
 
 (defun mixi-topic-comment-list-page (topic)
-  (concat "/view_bbs.pl?page=all"
-         "&id=" (number-to-string (mixi-topic-id topic))
-         "&comm_id=" (number-to-string
-                       (mixi-community-id (mixi-topic-community topic)))))
+  (concat "/view_bbs.pl?page=%d"
+         "&id=" (mixi-topic-id topic)
+         "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
 
 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
 (defconst mixi-topic-comment-list-regexp
@@ -1346,24 +1456,27 @@ while `mixi' is waiting for a server's response."
 </td>
 </tr>")
 
-(defun mixi-get-comments (parent)
+(defun mixi-get-comments (parent &optional max-numbers)
   "Get comments of PARENT."
   (unless (mixi-object-p parent)
-    (signal 'wrong-type-argument (list 'mixi-object-p object)))
+    (signal 'wrong-type-argument (list 'mixi-object-p parent)))
   (let* ((name (mixi-object-name parent))
         (list-page (intern (concat mixi-object-prefix name
                                    "-comment-list-page")))
         (regexp (eval (intern (concat mixi-object-prefix name
                                       "-comment-list-regexp")))))
-    (let ((items (mixi-get-matched-items (funcall list-page parent) 1 regexp)))
+    (let ((items (mixi-get-matched-items
+                 (funcall list-page parent) max-numbers regexp)))
       (mapcar (lambda (item)
                (mixi-make-comment parent (mixi-make-friend
                                           (nth 6 item) (nth 7 item))
-                                  (encode-time 0 (nth 4 item)
-                                               (nth 3 item)
-                                               (nth 2 item)
-                                               (nth 1 item)
-                                               (nth 0 item))
+                                  (encode-time
+                                   0
+                                   (string-to-number (nth 4 item))
+                                   (string-to-number (nth 3 item))
+                                   (string-to-number (nth 2 item))
+                                   (string-to-number (nth 1 item))
+                                   (string-to-number (nth 0 item)))
                                   (mixi-remove-markup (nth 8 item))))
              items))))
 
@@ -1373,11 +1486,10 @@ while `mixi' is waiting for a server's response."
 (defconst mixi-new-comment-list-regexp
   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
 
-(defvar mixi-new-comment-max-pages nil)
-(defun mixi-get-new-comments ()
+(defun mixi-get-new-comments (&optional max-numbers)
   "Get new comments."
   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
-                                      mixi-new-comment-max-pages
+                                      max-numbers
                                       mixi-new-comment-list-regexp)))
     (mapcar (lambda (item)
              (let ((diary (mixi-make-diary
@@ -1386,26 +1498,156 @@ while `mixi' is waiting for a server's response."
                (mixi-get-comments diary)))
            items)))
 
-;;
-
-;; FIXME: Move to the class method.
-
-;; FIXME: When it got some results, this function doesn't work fine.
-(defun mixi-friend-to-id (friend)
-  (with-mixi-retrieve (concat "/search.pl?submit=main&nickname="
-                             (w3m-url-encode-string friend
-                                                    mixi-coding-system))
-    (when (string-match "show_friend\\.pl\\?id=\\([0-9]+\\)" buffer)
-      (string-to-number (match-string 1 buffer)))))
-
-;; FIXME: When it got some results, this function doesn't work fine.
-(defun mixi-community-to-id (community)
-  (with-mixi-retrieve (concat "/search_community.pl?sort="
-                             "&type=com&submit=main&keyword="
-                             (w3m-url-encode-string community
-                                                    mixi-coding-system))
-    (when (string-match "view_community\\.pl\\?id=\\([0-9]+\\)" buffer)
-      (string-to-number (match-string 1 buffer)))))
+;; Message object.
+(defvar mixi-message-cache (make-hash-table :test 'equal))
+(defun mixi-make-message (id box)
+  "Return a message object."
+  (mixi-make-cache (list id box)
+                  (cons 'mixi-message (vector nil id box nil nil nil nil))
+                  mixi-message-cache))
+
+(defmacro mixi-message-p (message)
+  `(eq (mixi-object-class ,message) 'mixi-message))
+
+(defmacro mixi-message-page (message)
+  `(concat "/view_message.pl?id=" (mixi-message-id ,message)
+          "&box=" (mixi-message-box ,message)))
+
+(defconst mixi-message-owner-regexp
+  "<font COLOR=#996600>º¹½Ð¿Í</font>&nbsp;:&nbsp;<a HREF=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>")
+(defconst mixi-message-title-regexp
+  "<font COLOR=#996600>·ï¡¡Ì¾</font>&nbsp;:&nbsp;\\(.+\\)
+</td>")
+(defconst mixi-message-time-regexp
+  "<font COLOR=#996600>Æü¡¡ÉÕ</font>&nbsp;:&nbsp;\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\)»þ\\([0-9]+\\)ʬ&nbsp;&nbsp;")
+(defconst mixi-message-content-regexp
+  "<tr><td CLASS=h120>\\(.+\\)</td></tr>")
+
+(defun mixi-message-realize (message)
+  "Realize a MESSAGE."
+  (unless (mixi-message-realize-p message)
+    (with-mixi-retrieve (mixi-message-page message)
+      (if (string-match mixi-message-owner-regexp buffer)
+         (mixi-message-set-owner message
+                                 (mixi-make-friend (match-string 1 buffer)
+                                                   (match-string 2 buffer)))
+       (signal 'error (list 'cannot-find-owner message)))
+      (if (string-match mixi-message-title-regexp buffer)
+         (mixi-message-set-title message (match-string 1 buffer))
+       (signal 'error (list 'cannot-find-title message)))
+      (if (string-match mixi-message-time-regexp buffer)
+         (mixi-message-set-time
+          message (encode-time 0 (string-to-number (match-string 5 buffer))
+                               (string-to-number (match-string 4 buffer))
+                               (string-to-number (match-string 3 buffer))
+                               (string-to-number (match-string 2 buffer))
+                               (string-to-number (match-string 1 buffer))))
+       (signal 'error (list 'cannot-find-time message)))
+      (if (string-match mixi-message-content-regexp buffer)
+         (mixi-message-set-content message (mixi-remove-markup
+                                            (match-string 1 buffer)))
+       (signal 'error (list 'cannot-find-content message))))
+    (mixi-message-touch message)))
+
+(defun mixi-message-realize-p (message)
+  "Return the timestamp of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aref (cdr message) 0))
+
+(defun mixi-message-id (message)
+  "Return the id of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aref (cdr message) 1))
+
+(defun mixi-message-box (message)
+  "Return the box of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aref (cdr message) 2))
+
+(defun mixi-message-owner (message)
+  "Return the owner of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (mixi-message-realize message)
+  (aref (cdr message) 3))
+
+(defun mixi-message-title (message)
+  "Return the title of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (mixi-message-realize message)
+  (aref (cdr message) 4))
+
+(defun mixi-message-time (message)
+  "Return the date of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (mixi-message-realize message)
+  (aref (cdr message) 5))
+
+(defun mixi-message-content (message)
+  "Return the content of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (mixi-message-realize message)
+  (aref (cdr message) 6))
+
+(defun mixi-message-touch (message)
+  "Set the timestamp of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aset (cdr message) 0 (current-time)))
+
+(defun mixi-message-set-owner (message owner)
+  "Set the owner of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aset (cdr message) 3 owner))
+
+(defun mixi-message-set-title (message title)
+  "Set the title of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aset (cdr message) 4 title))
+
+(defun mixi-message-set-time (message time)
+  "Set the date of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aset (cdr message) 5 time))
+
+(defun mixi-message-set-content (message content)
+  "Set the content of MESSAGE."
+  (unless (mixi-message-p message)
+    (signal 'wrong-type-argument (list 'mixi-message-p message)))
+  (aset (cdr message) 6 content))
+
+(defmacro mixi-message-list-page (&optional box)
+  `(concat "/list_message.pl?page=%d"
+          (when ,box (concat "&box=" ,box))))
+
+(defconst mixi-message-list-regexp
+  "<td><a HREF=\"view_message\\.pl\\?id=\\(.+\\)&box=\\(.+\\)\">")
+
+(defun mixi-get-messages (&rest args)
+  "Get messages."
+  (when (> (length args) 2)
+    (signal 'wrong-number-of-arguments (list 'mixi-get-messages
+                                            (length args))))
+  (let ((box (nth 0 args))
+       (max-numbers (nth 1 args)))
+    (when (or (not (stringp box)) (stringp max-numbers))
+      (setq box (nth 1 args))
+      (setq max-numbers (nth 0 args)))
+    (let ((items (mixi-get-matched-items (mixi-message-list-page box)
+                                        max-numbers
+                                        mixi-message-list-regexp)))
+      (mapcar (lambda (item)
+               (mixi-make-message (nth 0 item) (nth 1 item)))
+             items))))
 
 (provide 'mixi)