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