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