3849ad5854c622de083e8dea85ba391912b84793
[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-get-communities
35 ;;  * mixi-get-topics
36 ;;  * mixi-get-new-topics
37 ;;  * mixi-get-comments
38 ;;  * mixi-get-new-comments
39
40 ;; Example:
41 ;;
42 ;; Display only the first page of new diaries like a mail format.
43 ;;
44 ;; (let ((mixi-new-diary-max-pages 1)
45 ;;       (buffer (generate-new-buffer "*temp*"))
46 ;;       (format "%Y/%m/%d %H:%M"))
47 ;;   (pop-to-buffer buffer)
48 ;;   (mapc (lambda (diary)
49 ;;        (let ((subject (mixi-diary-title diary))
50 ;;              ;; Don't get owner's nick at first for omitting a useless
51 ;;              ;; retrieval.
52 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
53 ;;              (date (format-time-string format (mixi-diary-time diary)))
54 ;;              (body (mixi-diary-content diary)))
55 ;;          (insert "From: " from "\n"
56 ;;                  "Subject: " subject "\n"
57 ;;                  "Date: " date "\n\n"
58 ;;                  body "\n\n")))
59 ;;      (mixi-get-new-diaries))
60 ;;   (set-buffer-modified-p nil)
61 ;;   (setq buffer-read-only t)
62 ;;   (goto-char (point-min)))
63 ;;
64 ;; Display only the first page of new diaries including all comments like a
65 ;; mail format.  Comments are displayed like a reply mail.
66 ;;
67 ;; (let ((mixi-new-diary-max-pages 1)
68 ;;       (buffer (generate-new-buffer "*temp*"))
69 ;;       (format "%Y/%m/%d %H:%M"))
70 ;;   (pop-to-buffer buffer)
71 ;;   (mapc (lambda (diary)
72 ;;        (let ((subject (mixi-diary-title diary))
73 ;;              ;; Don't get owner's nick at first for omitting a useless
74 ;;              ;; retrieval.
75 ;;              (from (mixi-friend-nick (mixi-diary-owner diary)))
76 ;;              (date (format-time-string format (mixi-diary-time diary)))
77 ;;              (body (mixi-diary-content diary)))
78 ;;          (insert "From: " from "\n"
79 ;;                  "Subject: " subject "\n"
80 ;;                  "Date: " date "\n\n"
81 ;;                  body "\n\n")
82 ;;          (mapc (lambda (comment)
83 ;;                  (let ((from (mixi-friend-nick
84 ;;                               (mixi-comment-owner comment)))
85 ;;                        (subject (concat "Re: " subject))
86 ;;                        (date (format-time-string
87 ;;                               format (mixi-comment-time comment)))
88 ;;                        (body (mixi-comment-content comment)))
89 ;;                    (insert "From: " from "\n"
90 ;;                            "Subject: " subject "\n"
91 ;;                            "Date: " date "\n\n"
92 ;;                            body "\n\n")))
93 ;;                (mixi-get-comments diary))))
94 ;;      (mixi-get-new-diaries))
95 ;;   (set-buffer-modified-p nil)
96 ;;   (setq buffer-read-only t)
97 ;;   (goto-char (point-min)))
98
99 ;;; Code:
100
101 (eval-when-compile (require 'cl))
102 (autoload 'w3m-expand-url "w3m" nil t)
103
104 (defgroup mixi nil
105   "API library for accessing to mixi."
106   :group 'hypermedia)
107
108 (defcustom mixi-url "http://mixi.jp"
109   "*The URL of mixi."
110   :type 'string
111   :group 'mixi)
112
113 (defcustom mixi-coding-system 'euc-jp
114   "*Coding system for mixi."
115   :type 'coding-system
116   :group 'mixi)
117
118 (defcustom mixi-retrieve-function
119   (if (and (require 'url "url" t)
120            (fboundp 'url-retrieve-synchronously))
121       'mixi-w3-retrieve 'mixi-w3m-retrieve)
122   "*The function for retrieving."
123   :type '(choice (const :tag "Using w3" mixi-w3-retrieve)
124                  (const :tag "Using w3m" mixi-w3m-retrieve)
125                  (function :format "Other function: %v\n" :size 0))
126   :group 'mixi)
127
128 (defcustom mixi-default-email nil
129   "*Default E-mail address that is used to login automatically."
130   :type '(choice (string :tag "E-mail address")
131                  (const :tag "Asked when it is necessary" nil))
132   :group 'mixi)
133
134 (defcustom mixi-default-password nil
135   "*Default password that is used to login automatically."
136   :type '(choice (string :tag "Password")
137                  (const :tag "Asked when it is necessary" nil))
138   :group 'mixi)
139
140 (defcustom mixi-accept-adult-contents t
141   "*If non-nil, accept to access to the adult contents."
142   :type 'boolean
143   :group 'mixi)
144
145 (defcustom mixi-continuously-access-interval 4.0
146   "*Time interval between each mixi access.
147 Increase this value when unexpected error frequently occurs."
148   :type 'number
149   :group 'mixi)
150
151 (defcustom mixi-cache-expires 3600
152   "*Seconds for expiration of a cached object."
153   :type '(choice (integer :tag "Expired seconds")
154                  (const :tag "Don't expire" nil))
155   :group 'mixi)
156
157 ;; FIXME: Not implemented.
158 (defcustom mixi-cache-use-file t
159   "*If non-nil, caches are saved to files."
160   :type 'boolean
161   :group 'mixi)
162
163 (defcustom mixi-cache-directory (expand-file-name "~/.mixi")
164   "*Where to look for cache files."
165   :type 'directory
166   :group 'mixi)
167
168 (defvar mixi-me nil)
169
170 ;; Utilities.
171 (defmacro mixi-message (string)
172   `(concat "[mixi] " ,string))
173
174 (defconst mixi-message-adult-contents
175   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
176 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
177 (defconst mixi-message-continuously-accessing
178   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
179 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
180 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
181 (defconst mixi-warning-continuously-accessing
182   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
183 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
184 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
185
186 (defun mixi-retrieve-1 (buffer url &optional post-data)
187   (when (string-match mixi-message-adult-contents buffer)
188     (if mixi-accept-adult-contents
189         (setq buffer (funcall mixi-retrieve-function url "submit=agree"))
190       (setq buffer (funcall mixi-retrieve-function (concat url "?")))))
191   (when (string-match mixi-warning-continuously-accessing buffer)
192     (error (mixi-message "Continuously accessing")))
193   (if (not (string-match mixi-message-continuously-accessing buffer))
194       buffer
195     (message (mixi-message "Waiting for continuously accessing..."))
196     (sit-for mixi-continuously-access-interval)
197     (funcall mixi-retrieve-function url post-data)))
198
199 (defun mixi-w3-retrieve (url &optional post-data)
200   "Retrieve the URL and return getted strings."
201   (if post-data
202       (progn
203         (setq url-request-method "POST")
204         (setq url-request-data post-data))
205     (setq url-request-method "GET")
206     (setq url-request-data nil))
207   (let* ((url (url-expand-file-name url mixi-url))
208          (buffer (url-retrieve-synchronously url))
209          ret)
210     (unless (bufferp buffer)
211       (error (mixi-message "Cannot retrieve")))
212     (with-current-buffer buffer
213       (goto-char (point-min))
214       (if (re-search-forward "HTTP/[0-9.]+ 302 Moved" nil t)
215           (if (re-search-forward
216                (concat "Location: " mixi-url "\\(.+\\)") nil t)
217               (setq ret (mixi-w3-retrieve (match-string 1) post-data))
218             (setq ret (mixi-w3-retrieve "/home.pl" post-data)))
219         (unless (re-search-forward "HTTP/[0-9.]+ 200 OK" nil t)
220           (error (mixi-message "Cannot retrieve")))
221         (search-forward "\n\n")
222         (setq ret (mm-decode-coding-string
223                    (buffer-substring-no-properties (point) (point-max))
224                    mixi-coding-system))
225         (kill-buffer buffer)
226         (setq ret (mixi-retrieve-1 ret url post-data))))
227     ret))
228
229 (defun mixi-w3m-retrieve (url &optional post-data)
230   "Retrieve the URL and return getted strings."
231   (let ((url (w3m-expand-url url mixi-url)))
232     (with-temp-buffer
233       (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
234           (error (mixi-message "Cannot retrieve"))
235         (w3m-decode-buffer url)
236         (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
237           (mixi-retrieve-1 ret url post-data))))))
238
239 (defconst mixi-my-id-regexp
240   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
241
242 (defun mixi-login (&optional email password)
243   "Login to mixi."
244   (when (and (eq mixi-retrieve-function 'mixi-w3m-retrieve)
245              (not w3m-use-cookies))
246     (error
247      (mixi-message
248       "Require to accept cookies.  Please set `w3m-use-cookies' to t.")))
249   (let ((email (or email mixi-default-email
250                    (read-from-minibuffer (mixi-message "Login Email: "))))
251         (password (or password mixi-default-password
252                       (read-passwd (mixi-message "Login Password: ")))))
253     (let ((buffer (funcall mixi-retrieve-function "/login.pl"
254                            (concat "email=" email
255                                    "&password=" password
256                                    "&next_url=/home.pl"
257                                    "&sticky=on"))))
258       (unless (string-match "url=/check\\.pl\\?n=" buffer)
259         (error (mixi-message "Cannot login")))
260       (setq buffer (funcall mixi-retrieve-function "/check.pl?n=home.pl"))
261       (if (string-match mixi-my-id-regexp buffer)
262           (setq mixi-me (mixi-make-friend
263                          (string-to-number (match-string 1 buffer))))
264         (error (mixi-message "Cannot login"))))))
265
266 (defun mixi-logout ()
267   (funcall mixi-retrieve-function "/logout.pl"))
268
269 (defmacro with-mixi-retrieve (url &rest body)
270   `(let (buffer)
271      (when ,url
272        (setq buffer (funcall mixi-retrieve-function ,url))
273        (when (string-match "login.pl" buffer)
274          (mixi-login)
275          (setq buffer (funcall mixi-retrieve-function ,url))))
276      ,@body))
277 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
278
279 (defun mixi-get-matched-items (url max-pages regexp)
280   "Get matched items to REGEXP in URL."
281   (let ((page 1)
282         ids)
283     (catch 'end
284       (while (or (null max-pages) (<= page max-pages))
285         (with-mixi-retrieve (format url page)
286           (let ((pos 0))
287             (while (string-match regexp buffer pos)
288               (let ((num 1)
289                     list)
290                 (while (match-string num buffer)
291                   (let ((string (match-string num buffer)))
292                     (save-match-data
293                       (when (string-match "^[0-9]+$" string)
294                         (setq string (string-to-number string))))
295                     (setq list (cons string list)))
296                   (incf num))
297               (setq ids (cons (reverse list) ids))
298               (setq pos (match-end (1- num)))))
299             (when (eq pos 0)
300               (throw 'end ids))))
301         (incf page)))
302     ;; FIXME: Sort? Now order by newest.
303     (reverse ids)))
304
305 ;; stolen (and modified) from shimbun.el
306 (defun mixi-remove-markup (string)
307   "Remove markups from STRING."
308   (with-temp-buffer
309     (insert string)
310     (save-excursion
311       (goto-char (point-min))
312       (while (search-forward "<!--" nil t)
313         (delete-region (match-beginning 0)
314                        (or (search-forward "-->" nil t)
315                            (point-max))))
316       (goto-char (point-min))
317       (while (re-search-forward "<[^>]+>" nil t)
318         (replace-match "" t t))
319       (goto-char (point-min))
320       (while (re-search-forward "\r" nil t)
321         (replace-match "\n" t t)))
322     (buffer-string)))
323
324 ;; Cache.
325
326 (defun mixi-cache-expired-p (object)
327   "Whether a cache of OBJECT is expired."
328   ;; FIXME: Use method instead of `(aref (cdr object) 0)'.
329   (let ((timestamp (aref (cdr object) 0)))
330     (unless (or (null mixi-cache-expires)
331                  (null timestamp))
332       (time-less-p (time-add timestamp
333                              (seconds-to-time mixi-cache-expires))
334                    (current-time)))))
335
336 (defun mixi-make-cache (key value table)
337   "Make a cache object and return it."
338   (let ((cache (gethash key table)))
339     (if (and cache (not (mixi-cache-expired-p cache)))
340         cache
341       (puthash key value table))))
342
343 ;; Object.
344 (defconst mixi-object-prefix "mixi-")
345
346 (defmacro mixi-object-class (object)
347   `(car-safe ,object))
348
349 (defmacro mixi-object-p (object)
350   `(eq (string-match (concat "^" mixi-object-prefix)
351                      (symbol-name (mixi-object-class ,object))) 0))
352
353 (defun mixi-object-name (object)
354   "Return the name of OBJECT."
355   (unless (mixi-object-p object)
356     (signal 'wrong-type-argument (list 'mixi-object-p object)))
357   (let ((class (mixi-object-class object)))
358     (substring (symbol-name class) (length mixi-object-prefix))))
359
360 (defun mixi-object-id (object)
361   "Return the id of OBJECT."
362   (unless (mixi-object-p object)
363     (signal 'wrong-type-argument (list 'mixi-object-p object)))
364   (let ((func (intern (concat mixi-object-prefix
365                               (mixi-object-name object) "-id"))))
366     (funcall func object)))
367
368 ;; Friend object.
369 (defvar mixi-friend-cache (make-hash-table :test 'equal))
370 (defun mixi-make-friend (id &optional nick)
371   "Return a friend object."
372   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick nil nil nil nil
373                                                  nil nil nil nil nil nil nil))
374                    mixi-friend-cache))
375
376 (defun mixi-make-me ()
377   (unless mixi-me
378     (with-mixi-retrieve "/home.pl"
379       (if (string-match mixi-my-id-regexp buffer)
380           (setq mixi-me
381                 (mixi-make-friend (string-to-number (match-string 1 buffer))))
382         (signal 'error (list 'who-am-i)))))
383   mixi-me)
384
385 (defmacro mixi-friend-p (friend)
386   `(eq (mixi-object-class ,friend) 'mixi-friend))
387
388 (defmacro mixi-friend-page (friend)
389   `(concat "/show_friend.pl?id=" (number-to-string (mixi-friend-id ,friend))))
390
391 (defconst mixi-friend-nick-regexp
392   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
393 (defconst mixi-friend-name-sex-regexp
394   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾\\(&nbsp;\\| \\)Á°</font></td>\n+<td WIDTH=345>\\([^<]+\\) (\\([Ã˽÷]\\)À­)</td>")
395 (defconst mixi-friend-address-regexp
396   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¸½½»½ê</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
397 (defconst mixi-friend-age-regexp
398   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ǯ\\(&nbsp;\\| \\)Îð</font></td>\n<td>\\([0-9]+\\)ºÐ\\(\n.+\n\\)?</td></tr>")
399 (defconst mixi-friend-birthday-regexp
400   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>ÃÂÀ¸Æü</font></td>\n<td>\\([0-9]+\\)·î\\([0-9]+\\)Æü\\(\n.+\n\\)?</td></tr>")
401 (defconst mixi-friend-blood-type-regexp
402   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>·ì±Õ·¿</font></td>\n<td>\\([ABO]B?\\)·¿\\(\n\n\\)?</td></tr>")
403 (defconst mixi-friend-birthplace-regexp
404   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½Ð¿ÈÃÏ</font>\n?</td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
405 (defconst mixi-friend-hobby-regexp
406   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼ñ\\(&nbsp;\\| \\)Ì£</font></td>\n<td>\\(.+\\)</td></tr>")
407 (defconst mixi-friend-job-regexp
408   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¿¦\\(&nbsp;\\| \\)¶È</font></td>\n<td>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
409 (defconst mixi-friend-organization-regexp
410   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>½ê\\(&nbsp;\\| \\)°</font></td>\n<td[^>]*>\\(.+\\)\\(\n.+\n\\)?</td></tr>")
411 (defconst mixi-friend-profile-regexp
412   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¼«¸Ê¾Ò²ð</font></td>\n<td CLASS=h120>\\(.+\\)</td></tr>")
413
414 (defun mixi-friend-realize (friend)
415   "Realize a FRIEND."
416   ;; FIXME: Check a expiration of cache?
417   (unless (mixi-friend-realize-p friend)
418     (let (buf)
419       (with-mixi-retrieve (mixi-friend-page friend)
420         (setq buf buffer))
421       (if (string-match mixi-friend-nick-regexp buf)
422           (mixi-friend-set-nick friend (match-string 1 buf))
423         (signal 'error (list 'cannot-find-nick friend)))
424       ;; For getting my profile.
425       (unless (string-match mixi-friend-name-sex-regexp buf)
426         (with-mixi-retrieve "/show_profile.pl"
427           (setq buf buffer)))
428       (if (string-match mixi-friend-name-sex-regexp buf)
429           (progn
430             (mixi-friend-set-name friend (match-string 2 buf))
431             (mixi-friend-set-sex friend
432                                  (if (string= (match-string 3 buf) "ÃË")
433                                      'male 'female)))
434         (signal 'error (list 'cannot-find-name-or-sex friend)))
435       (when (string-match mixi-friend-address-regexp buf)
436         (mixi-friend-set-address friend (match-string 1 buf)))
437       (when (string-match mixi-friend-age-regexp buf)
438         (mixi-friend-set-age
439          friend (string-to-number (match-string 2 buf))))
440       (when (string-match mixi-friend-birthday-regexp buf)
441         (mixi-friend-set-birthday
442          friend (list (string-to-number (match-string 1 buf))
443                       (string-to-number (match-string 2 buf)))))
444       (when (string-match mixi-friend-blood-type-regexp buf)
445         (mixi-friend-set-blood-type friend (intern (match-string 1 buf))))
446       (when (string-match mixi-friend-birthplace-regexp buf)
447         (mixi-friend-set-birthplace friend (match-string 1 buf)))
448       (when (string-match mixi-friend-hobby-regexp buf)
449         (mixi-friend-set-hobby
450          friend (split-string (match-string 2 buf) ", ")))
451       (when (string-match mixi-friend-job-regexp buf)
452         (mixi-friend-set-job friend (match-string 2 buf)))
453       (when (string-match mixi-friend-organization-regexp buf)
454         (mixi-friend-set-organization friend (match-string 2 buf)))
455       (when (string-match mixi-friend-profile-regexp buf)
456         (mixi-friend-set-profile
457          friend (mixi-remove-markup (match-string 1 buf)))))
458     (mixi-friend-touch friend)))
459
460 (defun mixi-friend-realize-p (friend)
461   "Return the timestamp of FRIEND."
462   (unless (mixi-friend-p friend)
463     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
464   (aref (cdr friend) 0))
465
466 (defun mixi-friend-id (friend)
467   "Return the id of FRIEND."
468   (unless (mixi-friend-p friend)
469     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
470   (aref (cdr friend) 1))
471
472 (defun mixi-friend-nick (friend)
473   "Return the nick of FRIEND."
474   (unless (mixi-friend-p friend)
475     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
476   (unless (aref (cdr friend) 2)
477     (mixi-friend-realize friend))
478   (aref (cdr friend) 2))
479
480 (defun mixi-friend-name (friend)
481   "Return the name of FRIEND."
482   (unless (mixi-friend-p friend)
483     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
484   (mixi-friend-realize friend)
485   (aref (cdr friend) 3))
486
487 (defun mixi-friend-sex (friend)
488   "Return the sex of FRIEND."
489   (unless (mixi-friend-p friend)
490     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
491   (mixi-friend-realize friend)
492   (aref (cdr friend) 4))
493
494 (defun mixi-friend-address (friend)
495   "Return the address of FRIEND."
496   (unless (mixi-friend-p friend)
497     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
498   (mixi-friend-realize friend)
499   (aref (cdr friend) 5))
500
501 (defun mixi-friend-age (friend)
502   "Return the age of FRIEND."
503   (unless (mixi-friend-p friend)
504     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
505   (mixi-friend-realize friend)
506   (aref (cdr friend) 6))
507
508 (defun mixi-friend-birthday (friend)
509   "Return the birthday of FRIEND."
510   (unless (mixi-friend-p friend)
511     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
512   (mixi-friend-realize friend)
513   (aref (cdr friend) 7))
514
515 (defun mixi-friend-blood-type (friend)
516   "Return the blood type of FRIEND."
517   (unless (mixi-friend-p friend)
518     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
519   (mixi-friend-realize friend)
520   (aref (cdr friend) 8))
521
522 (defun mixi-friend-birthplace (friend)
523   "Return the birthplace of FRIEND."
524   (unless (mixi-friend-p friend)
525     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
526   (mixi-friend-realize friend)
527   (aref (cdr friend) 9))
528
529 (defun mixi-friend-hobby (friend)
530   "Return the hobby of FRIEND."
531   (unless (mixi-friend-p friend)
532     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
533   (mixi-friend-realize friend)
534   (aref (cdr friend) 10))
535
536 (defun mixi-friend-job (friend)
537   "Return the job of FRIEND."
538   (unless (mixi-friend-p friend)
539     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
540   (mixi-friend-realize friend)
541   (aref (cdr friend) 11))
542
543 (defun mixi-friend-organization (friend)
544   "Return the organization of FRIEND."
545   (unless (mixi-friend-p friend)
546     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
547   (mixi-friend-realize friend)
548   (aref (cdr friend) 12))
549
550 (defun mixi-friend-profile (friend)
551   "Return the pforile of FRIEND."
552   (unless (mixi-friend-p friend)
553     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
554   (mixi-friend-realize friend)
555   (aref (cdr friend) 13))
556
557 (defun mixi-friend-touch (friend)
558   "Set the timestamp of FRIEND."
559   (unless (mixi-friend-p friend)
560     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
561   (aset (cdr friend) 0 (current-time)))
562
563 (defun mixi-friend-set-nick (friend nick)
564   "Set the nick of FRIEND."
565   (unless (mixi-friend-p friend)
566     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
567   (aset (cdr friend) 2 nick))
568
569 (defun mixi-friend-set-name (friend name)
570   "Set the name of FRIEND."
571   (unless (mixi-friend-p friend)
572     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
573   (aset (cdr friend) 3 name))
574
575 (defun mixi-friend-set-sex (friend sex)
576   "Set the sex of FRIEND."
577   (unless (mixi-friend-p friend)
578     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
579   (aset (cdr friend) 4 sex))
580
581 (defun mixi-friend-set-address (friend address)
582   "Set the address of FRIEND."
583   (unless (mixi-friend-p friend)
584     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
585   (aset (cdr friend) 5 address))
586
587 (defun mixi-friend-set-age (friend age)
588   "Set the age of FRIEND."
589   (unless (mixi-friend-p friend)
590     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
591   (aset (cdr friend) 6 age))
592
593 (defun mixi-friend-set-birthday (friend birthday)
594   "Set the birthday of FRIEND."
595   (unless (mixi-friend-p friend)
596     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
597   (aset (cdr friend) 7 birthday))
598
599 (defun mixi-friend-set-blood-type (friend blood-type)
600   "Set the blood type of FRIEND."
601   (unless (mixi-friend-p friend)
602     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
603   (aset (cdr friend) 8 blood-type))
604
605 (defun mixi-friend-set-birthplace (friend birthplace)
606   "Set the birthplace of FRIEND."
607   (unless (mixi-friend-p friend)
608     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
609   (aset (cdr friend) 9 birthplace))
610
611 (defun mixi-friend-set-hobby (friend hobby)
612   "Set the hobby of FRIEND."
613   (unless (mixi-friend-p friend)
614     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
615   (aset (cdr friend) 10 hobby))
616
617 (defun mixi-friend-set-job (friend job)
618   "Set the job of FRIEND."
619   (unless (mixi-friend-p friend)
620     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
621   (aset (cdr friend) 11 job))
622
623 (defun mixi-friend-set-organization (friend organization)
624   "Set the organization of FRIEND."
625   (unless (mixi-friend-p friend)
626     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
627   (aset (cdr friend) 12 organization))
628
629 (defun mixi-friend-set-profile (friend profile)
630   "Set the profile of FRIEND."
631   (unless (mixi-friend-p friend)
632     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
633   (aset (cdr friend) 13 profile))
634
635 (defmacro mixi-friend-list-page (&optional friend)
636   `(concat "/list_friend.pl?page=%d"
637            (when ,friend (concat "&id=" (number-to-string
638                                          (mixi-friend-id ,friend))))))
639
640 (defconst mixi-friend-list-id-regexp
641   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
642 (defconst mixi-friend-list-nick-regexp
643   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
644
645 (defvar mixi-friend-max-pages 10)
646 (defun mixi-get-friends (&optional friend)
647   "Get friends of FRIEND."
648   (unless (or (null friend) (mixi-friend-p friend))
649     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
650   (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
651                                      mixi-friend-max-pages
652                                      mixi-friend-list-id-regexp))
653         (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
654                                        mixi-friend-max-pages
655                                        mixi-friend-list-nick-regexp)))
656     (let ((index 0)
657           ret)
658       (while (< index (length ids))
659         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
660                                           (nth 0 (nth index nicks))) ret))
661         (incf index))
662       (reverse ret))))
663
664 ;; Favorite.
665 (defmacro mixi-favorite-list-page ()
666   `(concat "/list_bookmark.pl?page=%d"))
667
668 (defconst mixi-favorite-list-id-regexp
669   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
670 (defconst mixi-favorite-list-nick-regexp
671   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
672 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
673
674 (defvar mixi-favorite-max-pages nil)
675 (defun mixi-get-favorites ()
676   "Get favorites."
677   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
678                                      mixi-favorite-max-pages
679                                      mixi-favorite-list-id-regexp))
680         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
681                                        mixi-favorite-max-pages
682                                        mixi-favorite-list-nick-regexp)))
683     (let ((index 0)
684           ret)
685       (while (< index (length ids))
686         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
687                                           (nth 0 (nth index nicks))) ret))
688         (incf index))
689       (reverse ret))))
690
691 ;; Log object.
692 (defun mixi-make-log (friend time)
693   "Return a log object."
694   (cons 'mixi-log (vector friend time)))
695
696 (defmacro mixi-log-p (log)
697   `(eq (mixi-object-class ,log) 'mixi-log))
698
699 (defun mixi-log-friend (log)
700   "Return the friend of LOG."
701   (unless (mixi-log-p log)
702     (signal 'wrong-type-argument (list 'mixi-log-p log)))
703   (aref (cdr log) 0))
704
705 (defun mixi-log-time (log)
706   "Return the time of LOG."
707   (unless (mixi-log-p log)
708     (signal 'wrong-type-argument (list 'mixi-log-p log)))
709   (aref (cdr log) 1))
710
711 (defmacro mixi-log-list-page ()
712   `(concat "/show_log.pl"))
713
714 (defconst mixi-log-list-regexp
715   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a><br>")
716
717 (defvar mixi-log-max-pages 1)
718 (defun mixi-get-logs ()
719   "Get logs."
720   (let ((items (mixi-get-matched-items (mixi-log-list-page)
721                                        mixi-log-max-pages
722                                        mixi-log-list-regexp)))
723     (mapcar (lambda (item)
724               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
725                              (encode-time 0 (nth 4 item) (nth 3 item)
726                                           (nth 2 item) (nth 1 item)
727                                           (nth 0 item))))
728             items)))
729
730 ;; Diary object.
731 (defvar mixi-diary-cache (make-hash-table :test 'equal))
732 (defun mixi-make-diary (owner id)
733   "Return a diary object."
734   (let ((owner (or owner (mixi-make-me))))
735     (mixi-make-cache (list (mixi-friend-id owner) id)
736                      (cons 'mixi-diary (vector nil owner id nil nil nil))
737                      mixi-diary-cache)))
738
739 (defmacro mixi-diary-p (diary)
740   `(eq (mixi-object-class ,diary) 'mixi-diary))
741
742 (defmacro mixi-diary-page (diary)
743   `(concat "/view_diary.pl?id=" (number-to-string (mixi-diary-id ,diary))
744            "&owner_id=" (number-to-string (mixi-friend-id
745                                            (mixi-diary-owner ,diary)))))
746
747 ;; FIXME: Remove `¤µ¤ó'.
748 (defconst mixi-diary-owner-nick-regexp
749   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
750 (defconst mixi-diary-time-regexp
751   "<td ALIGN=center ROWSPAN=2 NOWRAP WIDTH=95 bgcolor=#FFD8B0>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
752 (defconst mixi-diary-title-regexp
753   "<td BGCOLOR=#FFF4E0 WIDTH=430>&nbsp;\\([^<]+\\)</td></tr>")
754 (defconst mixi-diary-content-regexp
755   "<td CLASS=h12>\\(.+\\)</td></tr>")
756
757 (defun mixi-diary-realize (diary)
758   "Realize a DIARY."
759   ;; FIXME: Check a expiration of cache?
760   (unless (mixi-diary-realize-p diary)
761     (with-mixi-retrieve (mixi-diary-page diary)
762       (if (string-match mixi-diary-owner-nick-regexp buffer)
763           (mixi-friend-set-nick (mixi-diary-owner diary)
764                                 (match-string 1 buffer))
765         (signal 'error (list 'cannot-find-owner-nick diary)))
766       (if (string-match mixi-diary-time-regexp buffer)
767           (mixi-diary-set-time
768            diary (encode-time 0 (string-to-number (match-string 5 buffer))
769                               (string-to-number (match-string 4 buffer))
770                               (string-to-number (match-string 3 buffer))
771                               (string-to-number (match-string 2 buffer))
772                               (string-to-number (match-string 1 buffer))))
773         (signal 'error (list 'cannot-find-time diary)))
774       (if (string-match mixi-diary-title-regexp buffer)
775           (mixi-diary-set-title diary (match-string 1 buffer))
776         (signal 'error (list 'cannot-find-title diary)))
777       (if (string-match mixi-diary-content-regexp buffer)
778           (mixi-diary-set-content diary (mixi-remove-markup
779                                          (match-string 1 buffer)))
780         (signal 'error (list 'cannot-find-content diary))))
781     (mixi-diary-touch diary)))
782
783 (defun mixi-diary-realize-p (diary)
784   "Return the timestamp of DIARY."
785   (unless (mixi-diary-p diary)
786     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
787   (aref (cdr diary) 0))
788
789 (defun mixi-diary-owner (diary)
790   "Return the owner of DIARY."
791   (unless (mixi-diary-p diary)
792     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
793   (aref (cdr diary) 1))
794
795 (defun mixi-diary-id (diary)
796   "Return the id of DIARY."
797   (unless (mixi-diary-p diary)
798     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
799   (aref (cdr diary) 2))
800
801 (defun mixi-diary-time (diary)
802   "Return the time of DIARY."
803   (unless (mixi-diary-p diary)
804     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
805   (mixi-diary-realize diary)
806   (aref (cdr diary) 3))
807
808 (defun mixi-diary-title (diary)
809   "Return the title of DIARY."
810   (unless (mixi-diary-p diary)
811     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
812   (mixi-diary-realize diary)
813   (aref (cdr diary) 4))
814
815 (defun mixi-diary-content (diary)
816   "Return the content of DIARY."
817   (unless (mixi-diary-p diary)
818     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
819   (mixi-diary-realize diary)
820   (aref (cdr diary) 5))
821
822 (defun mixi-diary-touch (diary)
823   "Set the timestamp of DIARY."
824   (unless (mixi-diary-p diary)
825     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
826   (aset (cdr diary) 0 (current-time)))
827
828 (defun mixi-diary-set-time (diary time)
829   "Set the time of DIARY."
830   (unless (mixi-diary-p diary)
831     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
832   (aset (cdr diary) 3 time))
833
834 (defun mixi-diary-set-title (diary title)
835   "Set the title of DIARY."
836   (unless (mixi-diary-p diary)
837     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
838   (aset (cdr diary) 4 title))
839
840 (defun mixi-diary-set-content (diary content)
841   "Set the content of DIARY."
842   (unless (mixi-diary-p diary)
843     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
844   (aset (cdr diary) 5 content))
845
846 (defmacro mixi-diary-list-page (&optional friend)
847   `(concat "/list_diary.pl?page=%d"
848            (when ,friend (concat "&id=" (number-to-string
849                                          (mixi-friend-id ,friend))))))
850
851 (defconst mixi-diary-list-regexp
852   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
853
854 (defvar mixi-diary-max-pages nil)
855 (defun mixi-get-diaries (&optional friend)
856   "Get diaries of FRIEND."
857   (unless (or (null friend) (mixi-friend-p friend))
858     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
859   (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
860                                        mixi-diary-max-pages
861                                        mixi-diary-list-regexp)))
862     (mapcar (lambda (item)
863               (mixi-make-diary friend (nth 0 item)))
864             items)))
865
866 (defmacro mixi-new-diary-list-page ()
867   `(concat "/new_friend_diary.pl?page=%d"))
868
869 (defconst mixi-new-diary-list-regexp
870   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
871
872 (defvar mixi-new-diary-max-pages nil)
873 (defun mixi-get-new-diaries ()
874   "Get new diaries."
875   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
876                                        mixi-new-diary-max-pages
877                                        mixi-new-diary-list-regexp)))
878     (mapcar (lambda (item)
879               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
880             items)))
881
882 ;; Community object.
883 (defvar mixi-community-cache (make-hash-table :test 'equal))
884 (defun mixi-make-community (id &optional name)
885   "Return a community object."
886   (mixi-make-cache id (cons 'mixi-community (vector nil id name nil nil nil
887                                                     nil nil nil nil))
888                    mixi-community-cache))
889
890 (defmacro mixi-community-p (community)
891   `(eq (mixi-object-class ,community) 'mixi-community))
892
893 (defmacro mixi-community-page (community)
894   `(concat "/view_community.pl?id=" (number-to-string
895                                      (mixi-community-id ,community))))
896
897 (defconst mixi-community-nodata-regexp
898   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
899 (defconst mixi-community-name-regexp
900   "<td WIDTH=345>\\(.*\\)</td></tr>")
901 (defconst mixi-community-birthday-regexp
902   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>³«ÀßÆü</font></td>\n<td>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
903 ;; FIXME: Care when the owner has seceded.
904 (defconst mixi-community-owner-regexp
905   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>´ÉÍý¿Í</font></td>\n<td>\n\n<a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\n\\(.+\\)</a>")
906 (defconst mixi-community-category-regexp
907   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\n<td>\\([^<]+\\)</td>")
908 (defconst mixi-community-members-regexp
909   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥á¥ó¥Ð¡¼¿ô</font></td>\n<td>\\([0-9]+\\)¿Í</td></tr>")
910 (defconst mixi-community-open-level-regexp
911   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>»²²Ã¾ò·ï¤È<br>¸ø³«¥ì¥Ù¥ë</font></td>
912 <td>\\(.+\\)</td></tr>")
913 (defconst mixi-community-authority-regexp
914   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥È¥Ô¥Ã¥¯ºîÀ®¤Î¸¢¸Â</font></td>\n<td>\\(.+\\)</td></tr>")
915 (defconst mixi-community-description-regexp
916   "<td CLASS=h120>\\(.+\\)</td>")
917
918 (defun mixi-community-realize (community)
919   "Realize a COMMUNITY."
920   ;; FIXME: Check a expiration of cache?
921   (unless (mixi-community-realize-p community)
922     (with-mixi-retrieve (mixi-community-page community)
923       (if (string-match mixi-community-nodata-regexp buffer)
924           ;; FIXME: Set all members?
925           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
926         (if (string-match mixi-community-name-regexp buffer)
927             (mixi-community-set-name community (match-string 1 buffer))
928           (signal 'error (list 'cannot-find-name community)))
929         (if (string-match mixi-community-birthday-regexp buffer)
930             (mixi-community-set-birthday
931              community
932              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
933                           (string-to-number (match-string 2 buffer))
934                           (string-to-number (match-string 1 buffer))))
935           (signal 'error (list 'cannot-find-birthday community)))
936         (if (string-match mixi-community-owner-regexp buffer)
937             (if (string= (match-string 1 buffer) "home.pl")
938                 (mixi-community-set-owner community (mixi-make-me))
939               (mixi-community-set-owner
940                community (mixi-make-friend
941                           (string-to-number (match-string 2 buffer))
942                           (match-string 3 buffer))))
943           (signal 'error (list 'cannot-find-owner community)))
944         (if (string-match mixi-community-category-regexp buffer)
945             (mixi-community-set-category community (match-string 1 buffer))
946           (signal 'error (list 'cannot-find-category community)))
947         (if (string-match mixi-community-members-regexp buffer)
948             (mixi-community-set-members
949              community (string-to-number (match-string 1 buffer)))
950           (signal 'error (list 'cannot-find-members community)))
951         (if (string-match mixi-community-open-level-regexp buffer)
952             (mixi-community-set-open-level community (match-string 1 buffer))
953           (signal 'error (list 'cannot-find-open-level community)))
954         (if (string-match mixi-community-authority-regexp buffer)
955             (mixi-community-set-authority community (match-string 1 buffer))
956           (signal 'error (list 'cannot-find-authority community)))
957         (if (string-match mixi-community-description-regexp buffer)
958             (mixi-community-set-description
959              community (mixi-remove-markup (match-string 1 buffer)))
960           (signal 'error (list 'cannot-find-description community)))))
961     (mixi-community-touch community)))
962
963 (defun mixi-community-realize-p (community)
964   "Return the timestamp of COMMUNITY."
965   (unless (mixi-community-p community)
966     (signal 'wrong-type-argument (list 'mixi-community-p community)))
967   (aref (cdr community) 0))
968
969 (defun mixi-community-id (community)
970   "Return the id of COMMUNITY."
971   (unless (mixi-community-p community)
972     (signal 'wrong-type-argument (list 'mixi-community-p community)))
973   (aref (cdr community) 1))
974
975 (defun mixi-community-name (community)
976   "Return the name of COMMUNITY."
977   (unless (mixi-community-p community)
978     (signal 'wrong-type-argument (list 'mixi-community-p community)))
979   (unless (aref (cdr community) 2)
980     (mixi-community-realize community))
981   (aref (cdr community) 2))
982
983 (defun mixi-community-birthday (community)
984   "Return the birthday of COMMUNITY."
985   (unless (mixi-community-p community)
986     (signal 'wrong-type-argument (list 'mixi-community-p community)))
987   (mixi-community-realize community)
988   (aref (cdr community) 3))
989
990 (defun mixi-community-owner (community)
991   "Return the owner of COMMUNITY."
992   (unless (mixi-community-p community)
993     (signal 'wrong-type-argument (list 'mixi-community-p community)))
994   (mixi-community-realize community)
995   (aref (cdr community) 4))
996
997 (defun mixi-community-category (community)
998   "Return the category of COMMUNITY."
999   (unless (mixi-community-p community)
1000     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1001   (mixi-community-realize community)
1002   (aref (cdr community) 5))
1003
1004 (defun mixi-community-members (community)
1005   "Return the members of COMMUNITY."
1006   (unless (mixi-community-p community)
1007     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1008   (mixi-community-realize community)
1009   (aref (cdr community) 6))
1010
1011 (defun mixi-community-open-level (community)
1012   "Return the open-level of COMMUNITY."
1013   (unless (mixi-community-p community)
1014     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1015   (mixi-community-realize community)
1016   (aref (cdr community) 7))
1017
1018 (defun mixi-community-authority (community)
1019   "Return the authority of COMMUNITY."
1020   (unless (mixi-community-p community)
1021     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1022   (mixi-community-realize community)
1023   (aref (cdr community) 8))
1024
1025 (defun mixi-community-description (community)
1026   "Return the description of COMMUNITY."
1027   (unless (mixi-community-p community)
1028     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1029   (mixi-community-realize community)
1030   (aref (cdr community) 9))
1031
1032 (defun mixi-community-touch (community)
1033   "Set the timestamp of COMMUNITY."
1034   (unless (mixi-community-p community)
1035     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1036   (aset (cdr community) 0 (current-time)))
1037
1038 (defun mixi-community-set-name (community name)
1039   "Set the name of COMMUNITY."
1040   (unless (mixi-community-p community)
1041     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1042   (aset (cdr community) 2 name))
1043
1044 (defun mixi-community-set-birthday (community birthday)
1045   "Set the birthday of COMMUNITY."
1046   (unless (mixi-community-p community)
1047     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1048   (aset (cdr community) 3 birthday))
1049
1050 (defun mixi-community-set-owner (community owner)
1051   "Set the owner of COMMUNITY."
1052   (unless (mixi-community-p community)
1053     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1054   (unless (mixi-friend-p owner)
1055     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1056   (aset (cdr community) 4 owner))
1057
1058 (defun mixi-community-set-category (community category)
1059   "Set the category of COMMUNITY."
1060   (unless (mixi-community-p community)
1061     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1062   (aset (cdr community) 5 category))
1063
1064 (defun mixi-community-set-members (community members)
1065   "Set the name of COMMUNITY."
1066   (unless (mixi-community-p community)
1067     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1068   (aset (cdr community) 6 members))
1069
1070 (defun mixi-community-set-open-level (community open-level)
1071   "Set the name of COMMUNITY."
1072   (unless (mixi-community-p community)
1073     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1074   (aset (cdr community) 7 open-level))
1075
1076 (defun mixi-community-set-authority (community authority)
1077   "Set the name of COMMUNITY."
1078   (unless (mixi-community-p community)
1079     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1080   (aset (cdr community) 8 authority))
1081
1082 (defun mixi-community-set-description (community description)
1083   "Set the name of COMMUNITY."
1084   (unless (mixi-community-p community)
1085     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1086   (aset (cdr community) 9 description))
1087
1088 (defmacro mixi-community-list-page (&optional friend)
1089   `(concat "/list_community.pl?page=%d"
1090            (when ,friend (concat "&id=" (number-to-string
1091                                          (mixi-friend-id ,friend))))))
1092
1093 (defconst mixi-community-list-id-regexp
1094   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
1095 (defconst mixi-community-list-name-regexp
1096   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
1097
1098 (defvar mixi-community-max-pages nil)
1099 (defun mixi-get-communities (&optional friend)
1100   "Get communities of FRIEND."
1101   (unless (or (null friend) (mixi-friend-p friend))
1102     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
1103   (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
1104                                      mixi-community-max-pages
1105                                      mixi-community-list-id-regexp))
1106         (names (mixi-get-matched-items (mixi-community-list-page friend)
1107                                      mixi-community-max-pages
1108                                      mixi-community-list-name-regexp)))
1109     (let ((index 0)
1110           ret)
1111       (while (< index (length ids))
1112         (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
1113                                              (nth 0 (nth index names))) ret))
1114         (incf index))
1115       (reverse ret))))
1116
1117 ;; Topic object.
1118 (defvar mixi-topic-cache (make-hash-table :test 'equal))
1119 (defun mixi-make-topic (community id)
1120   "Return a topic object."
1121   (mixi-make-cache (list (mixi-community-id community) id)
1122                    (cons 'mixi-topic (vector nil community id nil nil nil nil))
1123                    mixi-topic-cache))
1124
1125 (defmacro mixi-topic-p (topic)
1126   `(eq (mixi-object-class ,topic) 'mixi-topic))
1127
1128 (defmacro mixi-topic-page (topic)
1129   `(concat "/view_bbs.pl?id=" (number-to-string (mixi-topic-id ,topic))
1130            "&comm_id=" (number-to-string
1131                         (mixi-community-id (mixi-topic-community ,topic)))))
1132
1133 (defconst mixi-topic-time-regexp
1134   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
1135 (defconst mixi-topic-title-regexp
1136   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
1137 ;; FIXME: Remove `¤µ¤ó'.
1138 (defconst mixi-topic-owner-regexp
1139   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)\\(¤µ¤ó\\)?</a>")
1140 (defconst mixi-topic-content-regexp
1141   "<td class=\"h120\"><table><tr>\\(.+\\)?</tr></table>\\(.+\\)</td>")
1142
1143 (defun mixi-topic-realize (topic)
1144   "Realize a TOPIC."
1145   ;; FIXME: Check a expiration of cache?
1146   (unless (mixi-topic-realize-p topic)
1147     (with-mixi-retrieve (mixi-topic-page topic)
1148       (if (string-match mixi-topic-time-regexp buffer)
1149           (mixi-topic-set-time
1150            topic (encode-time 0 (string-to-number (match-string 5 buffer))
1151                               (string-to-number (match-string 4 buffer))
1152                               (string-to-number (match-string 3 buffer))
1153                               (string-to-number (match-string 2 buffer))
1154                               (string-to-number (match-string 1 buffer))))
1155         (signal 'error (list 'cannot-find-time topic)))
1156       (if (string-match mixi-topic-title-regexp buffer)
1157           (mixi-topic-set-title topic (match-string 1 buffer))
1158         (signal 'error (list 'cannot-find-title topic)))
1159       (if (string-match mixi-topic-owner-regexp buffer)
1160           (mixi-topic-set-owner topic
1161                                 (mixi-make-friend
1162                                  (string-to-number (match-string 1 buffer))
1163                                  (match-string 2 buffer)))
1164         (signal 'error (list 'cannot-find-owner topic)))
1165       (if (string-match mixi-topic-content-regexp buffer)
1166           (mixi-topic-set-content topic (mixi-remove-markup
1167                                          (match-string 2 buffer)))
1168         (signal 'error (list 'cannot-find-content topic))))
1169     (mixi-topic-touch topic)))
1170
1171 (defun mixi-topic-realize-p (topic)
1172   "Return the timestamp of TOPIC."
1173   (unless (mixi-topic-p topic)
1174     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1175   (aref (cdr topic) 0))
1176
1177 (defun mixi-topic-community (topic)
1178   "Return the community of TOPIC."
1179   (unless (mixi-topic-p topic)
1180     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1181   (aref (cdr topic) 1))
1182
1183 (defun mixi-topic-id (topic)
1184   "Return the id of TOPIC."
1185   (unless (mixi-topic-p topic)
1186     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1187   (aref (cdr topic) 2))
1188
1189 (defun mixi-topic-time (topic)
1190   "Return the time of TOPIC."
1191   (unless (mixi-topic-p topic)
1192     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1193   (mixi-topic-realize topic)
1194   (aref (cdr topic) 3))
1195
1196 (defun mixi-topic-title (topic)
1197   "Return the title of TOPIC."
1198   (unless (mixi-topic-p topic)
1199     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1200   (mixi-topic-realize topic)
1201   (aref (cdr topic) 4))
1202
1203 (defun mixi-topic-owner (topic)
1204   "Return the owner of TOPIC."
1205   (unless (mixi-topic-p topic)
1206     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1207   (mixi-topic-realize topic)
1208   (aref (cdr topic) 5))
1209
1210 (defun mixi-topic-content (topic)
1211   "Return the content of TOPIC."
1212   (unless (mixi-topic-p topic)
1213     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1214   (mixi-topic-realize topic)
1215   (aref (cdr topic) 6))
1216
1217 (defun mixi-topic-touch (topic)
1218   "Set the timestamp of TOPIC."
1219   (unless (mixi-topic-p topic)
1220     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1221   (aset (cdr topic) 0 (current-time)))
1222
1223 (defun mixi-topic-set-time (topic time)
1224   "Set the time of TOPIC."
1225   (unless (mixi-topic-p topic)
1226     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1227   (aset (cdr topic) 3 time))
1228
1229 (defun mixi-topic-set-title (topic title)
1230   "Set the title of TOPIC."
1231   (unless (mixi-topic-p topic)
1232     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1233   (aset (cdr topic) 4 title))
1234
1235 (defun mixi-topic-set-owner (topic owner)
1236   "Set the owner of TOPIC."
1237   (unless (mixi-topic-p topic)
1238     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1239   (unless (mixi-friend-p owner)
1240     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1241   (aset (cdr topic) 5 owner))
1242
1243 (defun mixi-topic-set-content (topic content)
1244   "Set the content of TOPIC."
1245   (unless (mixi-topic-p topic)
1246     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1247   (aset (cdr topic) 6 content))
1248
1249 (defmacro mixi-topic-list-page (community)
1250   `(concat "/list_bbs.pl?page=%d"
1251            "&id=" (number-to-string (mixi-community-id ,community))))
1252
1253 (defconst mixi-topic-list-regexp
1254   "<a href=view_bbs\\.pl\\?id=\\([0-9]+\\)")
1255
1256 (defvar mixi-topic-max-pages nil)
1257 (defun mixi-get-topics (community)
1258   "Get topics of COMMUNITY."
1259   (unless (mixi-community-p community)
1260     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1261   (let ((items (mixi-get-matched-items (mixi-topic-list-page community)
1262                                        mixi-topic-max-pages
1263                                        mixi-topic-list-regexp)))
1264     (mapcar (lambda (item)
1265               (mixi-make-topic community (nth 0 item)))
1266             items)))
1267
1268 (defmacro mixi-new-topic-list-page ()
1269   `(concat "/new_bbs.pl?page=%d"))
1270
1271 (defconst mixi-new-topic-list-regexp
1272   "<a href=\"view_bbs\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
1273
1274 (defvar mixi-new-topic-max-pages nil)
1275 (defun mixi-get-new-topics ()
1276   "Get new topics."
1277   (let ((items (mixi-get-matched-items (mixi-new-topic-list-page)
1278                                        mixi-new-topic-max-pages
1279                                        mixi-new-topic-list-regexp)))
1280     (mapcar (lambda (item)
1281               (mixi-make-topic (mixi-make-community (nth 1 item))
1282                                (nth 0 item)))
1283             items)))
1284
1285 ;; Comment object.
1286 (defun mixi-make-comment (parent owner time content)
1287   "Return a comment object."
1288   (cons 'mixi-comment (vector parent owner time content)))
1289
1290 (defmacro mixi-comment-p (comment)
1291   `(eq (mixi-object-class ,comment) 'mixi-comment))
1292
1293 (defun mixi-comment-parent (comment)
1294   "Return the parent of COMMENT."
1295   (unless (mixi-comment-p comment)
1296     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1297   (aref (cdr comment) 0))
1298
1299 (defun mixi-comment-owner (comment)
1300   "Return the owner of COMMENT."
1301   (unless (mixi-comment-p comment)
1302     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1303   (aref (cdr comment) 1))
1304
1305 (defun mixi-comment-time (comment)
1306   "Return the time of COMMENT."
1307   (unless (mixi-comment-p comment)
1308     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1309   (aref (cdr comment) 2))
1310
1311 (defun mixi-comment-content (comment)
1312   "Return the content of COMMENT."
1313   (unless (mixi-comment-p comment)
1314     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1315   (aref (cdr comment) 3))
1316
1317 (defun mixi-diary-comment-list-page (diary)
1318   (concat "/view_diary.pl?page=all"
1319           "&id=" (number-to-string (mixi-diary-id diary))
1320           "&owner_id=" (number-to-string
1321                         (mixi-friend-id (mixi-diary-owner diary)))))
1322
1323 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1324 (defconst mixi-diary-comment-list-regexp
1325 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
1326 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
1327 <input type=checkbox name=comment_id value=\".+\">
1328 \\|\\)
1329 </td>
1330 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
1331 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
1332 <tr>
1333 <td>
1334 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1335
1336 </td>
1337 </tr>
1338 </table>
1339 </td>
1340 </tr>
1341 <!-- ËÜʸ : start -->
1342 <tr>
1343 <td bgcolor=\"#ffffff\">
1344 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
1345 <tr>
1346 <td CLASS=h12>
1347 \\(.+\\)
1348 </td></tr></table>")
1349
1350 (defun mixi-topic-comment-list-page (topic)
1351   (concat "/view_bbs.pl?page=all"
1352           "&id=" (number-to-string (mixi-topic-id topic))
1353           "&comm_id=" (number-to-string
1354                         (mixi-community-id (mixi-topic-community topic)))))
1355
1356 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1357 (defconst mixi-topic-comment-list-regexp
1358   "<tr valign=\"top\">
1359 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
1360 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1361 \\([0-9]+\\):\\([0-9]+\\)<br>
1362 \\(</td>\\)
1363 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
1364 <b>&nbsp;&nbsp;[0-9]+</b>:</font>&nbsp;
1365
1366 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1367
1368
1369
1370 </td>
1371 </tr>
1372 <tr>
1373 <td bgcolor=\"#ffffff\" align=\"center\">
1374 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
1375 <tr>
1376 <td class=\"h120\">
1377
1378 \\(.+\\)
1379 </td>
1380 </tr>
1381 </table>
1382 </td>
1383 </tr>")
1384
1385 (defun mixi-get-comments (parent)
1386   "Get comments of PARENT."
1387   (unless (mixi-object-p parent)
1388     (signal 'wrong-type-argument (list 'mixi-object-p object)))
1389   (let* ((name (mixi-object-name parent))
1390          (list-page (intern (concat mixi-object-prefix name
1391                                     "-comment-list-page")))
1392          (regexp (eval (intern (concat mixi-object-prefix name
1393                                        "-comment-list-regexp")))))
1394     (let ((items (mixi-get-matched-items (funcall list-page parent) 1 regexp)))
1395       (mapcar (lambda (item)
1396                 (mixi-make-comment parent (mixi-make-friend
1397                                            (nth 6 item) (nth 7 item))
1398                                    (encode-time 0 (nth 4 item)
1399                                                 (nth 3 item)
1400                                                 (nth 2 item)
1401                                                 (nth 1 item)
1402                                                 (nth 0 item))
1403                                    (mixi-remove-markup (nth 8 item))))
1404               items))))
1405
1406 (defmacro mixi-new-comment-list-page ()
1407   `(concat "/new_comment.pl?page=%d"))
1408
1409 (defconst mixi-new-comment-list-regexp
1410   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
1411
1412 (defvar mixi-new-comment-max-pages nil)
1413 (defun mixi-get-new-comments ()
1414   "Get new comments."
1415   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
1416                                        mixi-new-comment-max-pages
1417                                        mixi-new-comment-list-regexp)))
1418     (mapcar (lambda (item)
1419               (let ((diary (mixi-make-diary
1420                             (mixi-make-friend (nth 1 item))
1421                             (nth 0 item))))
1422                 (mixi-get-comments diary)))
1423             items)))
1424
1425 (provide 'mixi)
1426
1427 ;;; mixi.el ends here