* sb-mixi.el (shimbun-mixi-message-id-suffix): New constant.
[elisp/mixi.git] / mixi.el
diff --git a/mixi.el b/mixi.el
index f42ea6f..587def2 100644 (file)
--- a/mixi.el
+++ b/mixi.el
 ;;  * mixi-get-new-comments
 ;;  * mixi-get-messages
 ;;  * mixi-get-introductions
+;;
+;; API for posting:
+;;
+;;  * mixi-post-diary
+;;  * mixi-post-topic
+;;  * mixi-post-comment
+;;  * mixi-post-message
 ;; 
 ;; Utilities:
 ;;
@@ -211,12 +218,25 @@ Increase this value when unexpected error frequently occurs."
 (put 'mixi-realization-error
      'error-conditions '(mixi-realization-error error))
 
+(put 'mixi-post-error
+     'error-message (mixi-message "Cannot post"))
+(put 'mixi-post-error
+     'error-conditions '(mixi-post-error error))
+
 (defmacro mixi-realization-error (type object)
   `(let ((data (if (and (boundp 'buffer) debug-on-error)
                   (list ,type ,object buffer)
                 (list ,type ,object))))
      (signal 'mixi-realization-error data)))
 
+(defmacro mixi-post-error (type &optional object)
+  `(let ((data (when (and (boundp 'buffer) debug-on-error)
+                (list buffer))))
+     (if ,object
+        (setq data (cons ,type (cons ,object data)))
+       (setq data (cons ,type data)))
+     (signal 'mixi-post-error data)))
+
 (defconst mixi-message-adult-contents
   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
@@ -232,6 +252,14 @@ Increase this value when unexpected error frequently occurs."
 (defmacro mixi-retrieve (url &optional post-data)
   `(funcall mixi-retrieve-function ,url ,post-data))
 
+;; FIXME: Change `mixi-retrieve-function' to `mixi-backend'.
+(defmacro mixi-post-form (url fields)
+  `(let ((name (symbol-name mixi-retrieve-function)))
+     (when (string-match "-\\([a-z]+\\)-" name)
+       (let ((func (intern (concat "mixi-" (match-string 1 name)
+                                  "-post-form"))))
+        (funcall func ,url ,fields)))))
+
 (defun mixi-parse-buffer (url buffer &optional post-data)
   (when (string-match mixi-message-adult-contents buffer)
     (if mixi-accept-adult-contents
@@ -251,15 +279,28 @@ Increase this value when unexpected error frequently occurs."
        ,url
      (concat mixi-url ,url)))
 
-(defun mixi-url-retrieve (url &optional post-data)
+;; FIXME: Support file, checkbox and so on.
+(defun mixi-make-form-data (fields)
+  "Make form data and return (CONTENT-TYPE . FORM-DATA)."
+  (let* ((boundary (apply 'format "--_%d_%d_%d" (current-time)))
+        (content-type (concat "multipart/form-data; boundary=" boundary))
+        (form-data
+         (mapconcat
+          (lambda (field)
+            (concat "--" boundary "\r\n"
+                    "Content-Disposition: form-data; name=\""
+                    (car field) "\"\r\n"
+                    "\r\n"
+                    (encode-coding-string (cdr field) mixi-coding-system)))
+          fields "\r\n")))
+    (cons content-type (concat form-data "\r\n--" boundary "--"))))
+
+(defun mixi-url-retrieve (url &optional post-data extra-headers)
   "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 (mixi-expand-url url))
+  (let* ((url-request-method (if post-data "POST" "GET"))
+        (url-request-data post-data)
+        (url-request-extra-headers extra-headers)
+        (url (mixi-expand-url url))
         (buffer (url-retrieve-synchronously url))
         ret)
     (unless (bufferp buffer)
@@ -275,6 +316,11 @@ Increase this value when unexpected error frequently occurs."
       (kill-buffer buffer)
       (mixi-parse-buffer url ret post-data))))
 
+(defun mixi-url-post-form (url fields)
+  (let* ((form-data (mixi-make-form-data fields))
+        (extra-headers `(("Content-Type" . ,(car form-data)))))
+    (mixi-url-retrieve url (cdr form-data) extra-headers)))
+
 (defun mixi-w3m-retrieve (url &optional post-data)
   "Retrieve the URL and return gotten strings."
   (let ((url (mixi-expand-url url)))
@@ -285,6 +331,10 @@ Increase this value when unexpected error frequently occurs."
        (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
          (mixi-parse-buffer url ret post-data))))))
 
+(defun mixi-w3m-post-form (url fields)
+  (let ((form-data (mixi-make-form-data fields)))
+    (mixi-w3m-retrieve url form-data)))
+
 (defun mixi-curl-retrieve (url &optional post-data)
   "Retrieve the URL and return gotten strings."
   (with-temp-buffer
@@ -361,6 +411,18 @@ Increase this value when unexpected error frequently occurs."
 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
 (put 'with-mixi-retrieve 'edebug-form-spec '(form body))
 
+(defmacro with-mixi-post-form (url fields &rest body)
+  `(let (buffer)
+     (when ,url
+       (setq buffer (mixi-post-form ,url ,fields))
+       (when (string-match "<form action=\"login\\.pl\" method=\"post\">"
+                          buffer)
+        (mixi-login)
+        (setq buffer (mixi-post-form ,url ,fields))))
+     ,@body))
+(put 'with-mixi-post-form 'lisp-indent-function 'defun)
+(put 'with-mixi-post-form 'edebug-form-spec '(form body))
+
 (defun mixi-get-matched-items (url regexp &optional range)
   "Get matched items to REGEXP in URL."
   (let ((page 1)
@@ -1199,6 +1261,39 @@ Increase this value when unexpected error frequently occurs."
              (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
            items)))
 
+(defmacro mixi-post-diary-page ()
+  `(concat "/add_diary.pl"))
+
+(defconst mixi-post-key-regexp
+  "<input type=\"?hidden\"? name=\"?post_key\"? value=\"\\([a-z0-9]+\\)\">")
+(defconst mixi-post-succeed-regexp
+  "<b>\\(ºîÀ®\\|½ñ¤­¹þ¤ß\\)¤¬´°Î»¤·¤Þ¤·¤¿¡£È¿±Ç¤Ë»þ´Ö¤¬¤«¤«¤ë¤³¤È¤¬¤¢¤ê¤Þ¤¹¤Î¤Ç¡¢É½¼¨¤µ¤ì¤Æ¤¤¤Ê¤¤¾ì¹ç¤Ï¾¯¡¹¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£</b>")
+
+;; FIXME: Support photos.
+(defun mixi-post-diary (title content)
+  "Post a diary."
+  (unless (stringp title)
+    (signal 'wrong-type-argument (list 'stringp title)))
+  (unless (stringp content)
+    (signal 'wrong-type-argument (list 'stringp content)))
+  (let ((fields `(("id" . ,(mixi-friend-id (mixi-make-me)))
+                 ("diary_title" . ,title)
+                 ("diary_body" . ,content)
+                 ("submit" . "main")))
+       post-key)
+    (with-mixi-post-form (mixi-post-diary-page) fields
+      (if (string-match mixi-post-key-regexp buffer)
+         (setq post-key (match-string 1 buffer))
+       (mixi-post-error 'cannot-find-key)))
+    (setq fields `(("post_key" . ,post-key)
+                  ("id" . ,(mixi-friend-id (mixi-make-me)))
+                  ("diary_title" . ,title)
+                  ("diary_body" . ,content)
+                  ("submit" . "confirm")))
+    (with-mixi-post-form (mixi-post-diary-page) fields
+      (unless (string-match mixi-post-succeed-regexp buffer)
+       (mixi-post-error 'cannot-find-succeed)))))
+
 ;; Community object.
 (defvar mixi-community-cache (make-hash-table :test 'equal))
 (defun mixi-make-community (id &optional name birthday owner category members
@@ -1599,6 +1694,34 @@ Increase this value when unexpected error frequently occurs."
     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
   (aset (cdr topic) 6 content))
 
+(defmacro mixi-post-topic-page (community)
+  `(concat "/add_bbs.pl?id=" (mixi-community-id community)))
+
+;; FIXME: Support photos.
+(defun mixi-post-topic (community title content)
+  "Post a topic to COMMUNITY."
+  (unless (mixi-community-p community)
+    (signal 'wrong-type-argument (list 'mixi-community-p community)))
+  (unless (stringp title)
+    (signal 'wrong-type-argument (list 'stringp title)))
+  (unless (stringp content)
+    (signal 'wrong-type-argument (list 'stringp content)))
+  (let ((fields `(("bbs_title" . ,title)
+                 ("bbs_body" . ,content)
+                 ("submit" . "main")))
+       post-key)
+    (with-mixi-post-form (mixi-post-topic-page community) fields
+      (if (string-match mixi-post-key-regexp buffer)
+         (setq post-key (match-string 1 buffer))
+       (mixi-post-error 'cannot-find-key community)))
+    (setq fields `(("post_key" . ,post-key)
+                  ("bbs_title" . ,title)
+                  ("bbs_body" . ,content)
+                  ("submit" . "confirm")))
+    (with-mixi-post-form (mixi-post-topic-page community) fields
+      (unless (string-match mixi-post-succeed-regexp buffer)
+       (mixi-post-error 'cannot-find-succeed community)))))
+
 ;; Event object.
 (defvar mixi-event-cache (make-hash-table :test 'equal))
 (defun mixi-make-event (community id &optional time title owner date place
@@ -1966,7 +2089,7 @@ Increase this value when unexpected error frequently occurs."
 </table>
 </td>
 </tr>
-<!-- ¥³¥á¥ó¥ÈËÜʸ : start -->
+<!-- [^ ]+ : start -->
 <tr>
 <td bgcolor=\"#ffffff\">
 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
@@ -2085,6 +2208,50 @@ Increase this value when unexpected error frequently occurs."
              (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
            items)))
 
+(defun mixi-post-diary-comment-page (diary)
+  (concat "/add_comment.pl?&diary_id=" (mixi-diary-id diary)))
+
+(defun mixi-post-topic-comment-page (topic)
+  (concat "/add_bbs_comment.pl?id=" (mixi-topic-id topic)
+         "&comm_id=" (mixi-community-id (mixi-topic-community topic))))
+
+(defun mixi-post-event-comment-page (event)
+  (concat "/add_event_comment.pl?id=" (mixi-event-id event)
+         "&comm_id=" (mixi-community-id (mixi-event-community event))))
+
+;; FIXME: Support photos.
+(defun mixi-post-comment (parent content)
+  "Post a comment to PARENT."
+  (unless (mixi-object-p parent)
+    (signal 'wrong-type-argument (list 'mixi-object-p parent)))
+  (unless (stringp content)
+    (signal 'wrong-type-argument (list 'stringp content)))
+  (let* ((name (mixi-object-name bbs))
+        (page (intern (concat mixi-object-prefix "-post" name
+                              "-comment-page")))
+        fields post-key)
+    (if (mixi-diary-p parent)
+       (setq fields
+             `(("owner_id" . ,(mixi-friend-id (mixi-diary-owner diary)))
+               ("comment_body" . ,content)))
+      (setq fields `(("comment" . ,content))))
+    (with-mixi-post-form (funcall page parent) fields
+      (if (string-match mixi-post-key-regexp buffer)
+         (setq post-key (match-string 1 buffer))
+       (mixi-post-error 'cannot-find-key bbs)))
+    (if (mixi-diary-p parent)
+       (setq fields
+             `(("post_key" . ,post-key)
+               ("owner_id" . ,(mixi-friend-id (mixi-diary-owner diary)))
+               ("comment_body" . ,content)
+               ("submit" . "confirm")))
+      (setq fields `(("post_key" . ,post-key)
+                    ("comment" . ,content)
+                    ("submit" . "confirm"))))
+    (with-mixi-post-form (funcall page parent) fields
+      (unless (string-match mixi-post-succeed-regexp buffer)
+       (mixi-post-error 'cannot-find-succeed bbs)))))
+
 ;; Message object.
 (defconst mixi-message-box-list '(inbox outbox savebox thrash)) ; thrash?
 
@@ -2248,6 +2415,40 @@ Increase this value when unexpected error frequently occurs."
                (mixi-make-message (nth 0 item) (nth 1 item)))
              items))))
 
+(defmacro mixi-post-message-page (friend)
+  `(concat "/send_message.pl?id=" (mixi-friend-id friend)))
+
+(defconst mixi-post-message-key-regexp
+  "<input name=post_key type=hidden value=\\([a-z0-9]+\\)>")
+
+(defconst mixi-post-message-succeed-regexp
+  "<b>Á÷¿®´°Î»</b>¤·¤Þ¤·¤¿¡£")
+
+(defun mixi-post-message (friend title content)
+  "Post a message to FRIEND."
+  (unless (mixi-friend-p friend)
+    (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
+  (unless (stringp title)
+    (signal 'wrong-type-argument (list 'stringp title)))
+  (unless (stringp content)
+    (signal 'wrong-type-argument (list 'stringp content)))
+  (let ((fields `(("subject" . ,title)
+                 ("body" . ,content)
+                 ("submit" . "main")))
+       post-key)
+    (with-mixi-post-form (mixi-post-message-page friend) fields
+      (if (string-match mixi-post-message-key-regexp buffer)
+         (setq post-key (match-string 1 buffer))
+       (mixi-post-error 'cannot-find-key friend)))
+    (setq fields `(("post_key" . ,post-key)
+                  ("subject" . ,title)
+                  ("body" . ,content)
+                  ("yes" . "¡¡Á÷¡¡¿®¡¡")
+                  ("submit" . "confirm")))
+    (with-mixi-post-form (mixi-post-message-page friend) fields
+      (unless (string-match mixi-post-message-succeed-regexp buffer)
+       (mixi-post-error 'cannot-find-succeed friend)))))
+
 ;; Introduction object.
 (defun mixi-make-introduction (parent owner content)
   "Return a introduction object."