Importing mixi.el.
[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 (require 'w3m)
102 (eval-when-compile (require 'cl))
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-default-email nil
119   "*Default E-mail address that is used to login automatically."
120   :type '(choice (string :tag "E-mail address")
121                  (const :tag "Asked when it is necessary" nil))
122   :group 'mixi)
123
124 (defcustom mixi-default-password nil
125   "*Default password that is used to login automatically."
126   :type '(choice (string :tag "Password")
127                  (const :tag "Asked when it is necessary" nil))
128   :group 'mixi)
129
130 (defcustom mixi-accept-adult-contents t
131   "*"
132   :type 'boolean
133   :group 'mixi)
134
135 (defcustom mixi-continuously-access-interval 3.0
136   "*Time interval between each Mixi access.
137 Increase this value when unexpected error frequently occurs."
138   :type 'number
139   :group 'mixi)
140
141 (defcustom mixi-cache-expires 3600
142   "*Seconds for expiration of a cached object."
143   :type '(choice (integer :tag "Expired seconds")
144                  (const :tag "Don't expire" nil))
145   :group 'mixi)
146
147 ;; FIXME: Not implemented.
148 (defcustom mixi-cache-use-file t
149   "*If non-nil, caches are saved to files."
150   :type 'boolean
151   :group 'mixi)
152
153 (defcustom mixi-cache-directory (expand-file-name "~/.mixi")
154   "*Where to look for cache files."
155   :type 'directory
156   :group 'mixi)
157
158 (defcustom mixi-friend-max-pages 10
159   "*Number of pages which is retrieved for friends."
160   :type '(choice (integer :tag "Number of pages")
161                  (const :tag "Infinity" nil))
162   :group 'mixi)
163
164 (defcustom mixi-favorite-max-pages nil
165   "*Number of pages which is retrieved for favorites."
166   :type '(choice (integer :tag "Number of pages")
167                  (const :tag "Infinity" nil))
168   :group 'mixi)
169
170 (defcustom mixi-log-max-pages 1
171   "*Number of pages which is retrieved for logs."
172   :type '(choice (integer :tag "Number of pages")
173                  (const :tag "Infinity" nil))
174   :group 'mixi)
175
176 (defcustom mixi-diary-max-pages nil
177   "*Number of pages which is retrieved for diaries."
178   :type '(choice (integer :tag "Number of pages")
179                  (const :tag "Infinity" nil))
180   :group 'mixi)
181
182 (defcustom mixi-new-diary-max-pages nil
183   "*Number of pages which is retrieved for new diaries."
184   :type '(choice (integer :tag "Number of pages")
185                  (const :tag "Infinity" nil))
186   :group 'mixi)
187
188 (defcustom mixi-community-max-pages nil
189   "*Number of pages which is retrieved for communities."
190   :type '(choice (integer :tag "Number of pages")
191                  (const :tag "Infinity" nil))
192   :group 'mixi)
193
194 (defcustom mixi-topic-max-pages nil
195   "*Number of pages which is retrieved for topics."
196   :type '(choice (integer :tag "Number of pages")
197                  (const :tag "Infinity" nil))
198   :group 'mixi)
199
200 (defcustom mixi-new-comment-max-pages nil
201   "*Number of pages which is retrieved for new comments."
202   :type '(choice (integer :tag "Number of pages")
203                  (const :tag "Infinity" nil))
204   :group 'mixi)
205
206 (defcustom mixi-new-topic-max-pages nil
207   "*Number of pages which is retrieved for new topics."
208   :type '(choice (integer :tag "Number of pages")
209                  (const :tag "Infinity" nil))
210   :group 'mixi)
211
212 ;; FIXME: defcustom regexp.
213
214 (defcustom mixi-verbose t
215   "*Flag controls whether `mixi' should be verbose.
216 If it is non-ni, the `w3m-verbose' variable will be bound to nil
217 while `mixi' is waiting for a server's response."
218   :type 'boolean
219   :group 'mixi)
220
221 (defvar mixi-me nil)
222
223 ;; Utilities.
224 (defmacro mixi-message (string)
225   `(concat "[Mixi] " ,string))
226
227 (defconst mixi-message-adult-contents
228   "¤³¤Î¥Ú¡¼¥¸¤«¤éÀè¤Ï¥¢¥À¥ë¥È¡ÊÀ®¿Í¸þ¤±¡Ë¥³¥ó¥Æ¥ó¥Ä¤¬´Þ¤Þ¤ì¤Æ¤¤¤Þ¤¹¡£<br>
229 ±ÜÍ÷¤ËƱ°Õ¤µ¤ì¤¿Êý¤Î¤ß¡¢Àè¤Ø¤ª¿Ê¤ß¤¯¤À¤µ¤¤¡£")
230 (defconst mixi-message-continuously-accessing
231   "°ÂÄꤷ¤Æ¥µ¥¤¥È¤Î±¿±Ä¤ò¤ª¤³¤Ê¤¦°Ù¡¢´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹<br>
232 ¿·¤ÏÀ©¸Â¤µ¤»¤Æ¤¤¤¿¤À¤¤¤Æ¤ª¤ê¤Þ¤¹¡£¤´ÌÂÏǤò¤ª¤«¤±¤¤¤¿¤·¤Þ¤¹¤¬¡¢¤·¤Ð¤é¤¯¤ª<br>
233 ÂÔ¤Á¤¤¤¿¤À¤¤¤Æ¤«¤éÁàºî¤ò¤ª¤³¤Ê¤Ã¤Æ¤¯¤À¤µ¤¤¡£")
234 (defconst mixi-warning-continuously-accessing
235   "´Ö³Ö¤ò¶õ¤±¤Ê¤¤Ï¢Â³Åª¤Ê¥Ú¡¼¥¸¤ÎÁ«°Ü¡¦¹¹¿·¤òÉÑÈˤˤª¤³¤Ê¤ï¤ì¤Æ¤¤¤ë¤³¤È¤¬¸«<br>
236 ¼õ¤±¤é¤ì¤Þ¤·¤¿¤Î¤Ç¡¢°ì»þŪ¤ËÁàºî¤òÄä»ß¤µ¤»¤Æ¤¤¤¿¤À¤­¤Þ¤¹¡£¿½¤·Ìõ¤´¤¶¤¤¤Þ<br>
237 ¤»¤ó¤¬¡¢¤·¤Ð¤é¤¯¤Î´Ö¤ªÂÔ¤Á¤¯¤À¤µ¤¤¡£")
238
239 (defun mixi-retrieve (url &optional post-data)
240   "Retrieve the URL and return getted strings."
241   (let ((url (w3m-expand-url url mixi-url)))
242     (with-temp-buffer
243       (let ((w3m-verbose (if mixi-verbose nil w3m-verbose)))
244         (if (not (string= (w3m-retrieve url nil nil post-data) "text/html"))
245             (error (mixi-message "Cannot retrieve"))
246           (w3m-decode-buffer url)
247           (let ((ret (buffer-substring-no-properties (point-min) (point-max))))
248             (when (string-match mixi-message-adult-contents ret)
249               (if mixi-accept-adult-contents
250                   (setq ret (mixi-retrieve url "submit=agree"))
251                 (setq ret (mixi-retrieve (concat url "?")))))
252             (when (string-match mixi-warning-continuously-accessing ret)
253               (error (mixi-message "Continuously accessing")))
254             (if (not (string-match mixi-message-continuously-accessing ret))
255                 ret
256               (message (mixi-message "Waiting for continuously accessing..."))
257               (sit-for mixi-continuously-access-interval)
258               (mixi-retrieve url post-data))))))))
259
260 (defconst mixi-my-id-regexp
261   "<a href=\"add_diary\\.pl\\?id=\\([0-9]+\\)")
262
263 (defun mixi-login (&optional email password)
264   "Login to Mixi."
265   (let ((email (or email mixi-default-email
266                    (read-from-minibuffer (mixi-message "Login Email: "))))
267         (password (or password mixi-default-password
268                       (read-passwd (mixi-message "Login Password: ")))))
269     (let (buffer)
270       (setq buffer (mixi-retrieve "/login.pl"
271                                   (concat "email=" email
272                                           "&password=" password
273                                           "&next_url=/home.pl"
274                                           "&sticky=on")))
275       (unless (string-match "url=/check\\.pl\\?n=" buffer)
276         (error (mixi-message "Cannot login")))
277       (setq buffer (mixi-retrieve "/check.pl?n=home.pl"))
278       (if (string-match mixi-my-id-regexp buffer)
279           (setq mixi-me (mixi-make-friend
280                          (string-to-number (match-string 1 buffer))))
281         (error (mixi-message "Cannot login"))))))
282
283 (defun mixi-logout ()
284   (mixi-retrieve "/logout.pl"))
285
286 (defmacro with-mixi-retrieve (url &rest body)
287   `(let (buffer)
288      (when ,url
289        (setq buffer (mixi-retrieve ,url))
290        (when (string-match "login.pl" buffer)
291          (mixi-login)
292          (setq buffer (mixi-retrieve ,url))))
293      ,@body))
294 (put 'with-mixi-retrieve 'lisp-indent-function 'defun)
295
296 (defun mixi-get-matched-items (url max-pages regexp)
297   "Get matched items to REGEXP in URL."
298   (let ((page 1)
299         ids)
300     (catch 'end
301       (while (or (null max-pages) (<= page max-pages))
302         (with-mixi-retrieve (format url page)
303           (let ((pos 0))
304             (while (string-match regexp buffer pos)
305               (let ((num 1)
306                     list)
307                 (while (match-string num buffer)
308                   (let ((string (match-string num buffer)))
309                     (save-match-data
310                       (when (string-match "^[0-9]+$" string)
311                         (setq string (string-to-number string))))
312                     (setq list (cons string list)))
313                   (incf num))
314               (setq ids (cons (reverse list) ids))
315               (setq pos (match-end (1- num)))))
316             (when (eq pos 0)
317               (throw 'end ids))))
318         (incf page)))
319     ;; FIXME: Sort? Now order by newest.
320     (reverse ids)))
321
322 (defun mixi-remove-markup (string)
323   "Remove markups from STRING."
324   (with-temp-buffer
325     (insert string)
326     (save-excursion
327       (goto-char (point-min))
328       (while (search-forward "<!--" nil t)
329         (delete-region (match-beginning 0)
330                        (or (search-forward "-->" nil t)
331                            (point-max))))
332       (goto-char (point-min))
333       (while (re-search-forward "<[^>]+>" nil t)
334         (replace-match "" t t))
335       (goto-char (point-min))
336       (while (re-search-forward "\r" nil t)
337         (replace-match "\n" t t)))
338     (w3m-decode-entities)
339     (buffer-string)))
340
341 ;; Cache.
342 ;; FIXME: Is Caches saved to files?
343
344 (defun mixi-cache-expired-p (object)
345   "Whether a cache of OBJECT is expired."
346   ;; FIXME: Use method instead of `(aref (cdr object) 0)'.
347   (let ((timestamp (aref (cdr object) 0)))
348     (unless (or (null mixi-cache-expires)
349                  (null timestamp))
350       (time-less-p (time-add timestamp
351                              (seconds-to-time mixi-cache-expires))
352                    (current-time)))))
353
354 (defun mixi-make-cache (key value table)
355   "Make a cache object and return it."
356   (let ((cache (gethash key table)))
357     (if (and cache (not (mixi-cache-expired-p cache)))
358         cache
359       (puthash key value table))))
360
361 ;; Object.
362 (defconst mixi-object-prefix "mixi-")
363
364 (defmacro mixi-object-class (object)
365   `(car-safe ,object))
366
367 (defmacro mixi-object-p (object)
368   `(eq (string-match (concat "^" mixi-object-prefix)
369                      (symbol-name (mixi-object-class ,object))) 0))
370
371 (defun mixi-object-name (object)
372   "Return the name of OBJECT."
373   (unless (mixi-object-p object)
374     (signal 'wrong-type-argument (list 'mixi-object-p object)))
375   (let ((class (mixi-object-class object)))
376     (substring (symbol-name class) (length mixi-object-prefix))))
377
378 (defun mixi-object-id (object)
379   "Return the id of OBJECT."
380   (unless (mixi-object-p object)
381     (signal 'wrong-type-argument (list 'mixi-object-p object)))
382   (let ((func (intern (concat mixi-object-prefix
383                               (mixi-object-name object) "-id"))))
384     (funcall func object)))
385
386 ;; Friend object.
387 (defvar mixi-friend-cache (make-hash-table :test 'equal))
388 (defun mixi-make-friend (id &optional nick)
389   "Return a friend object."
390   (mixi-make-cache id (cons 'mixi-friend (vector nil id nick nil))
391                    mixi-friend-cache))
392
393 (defun mixi-make-me ()
394   (unless mixi-me
395     (with-mixi-retrieve "/home.pl"
396       (if (string-match mixi-my-id-regexp buffer)
397           (setq mixi-me
398                 (mixi-make-friend (string-to-number (match-string 1 buffer))))
399         (signal 'error (list 'who-am-i)))))
400   mixi-me)
401
402 (defmacro mixi-friend-p (friend)
403   `(eq (mixi-object-class ,friend) 'mixi-friend))
404
405 (defmacro mixi-friend-page (friend)
406   `(concat "/show_friend.pl?id=" (number-to-string (mixi-friend-id ,friend))))
407
408 (defconst mixi-friend-nick-regexp
409   "<img alt=\"\\*\" src=\"http://img\\.mixi\\.jp/img/dot0\\.gif\" width=\"1\" height=\"5\"><br>\n\\(.*\\)¤µ¤ó([0-9]+)")
410 (defconst mixi-friend-name-regexp
411   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾&nbsp;Á°</font></td>\n<td WIDTH=345>\\([^<]+\\)</td>")
412 (defconst mixi-my-name-regexp
413   "<td BGCOLOR=#F2DDB7 WIDTH=80 NOWRAP><font COLOR=#996600>̾ Á°</font></td>\n\n<td WIDTH=345>\\([^<]+\\)</td>")
414
415 (defun mixi-friend-realize (friend)
416   "Realize a FRIEND."
417   ;; FIXME: Check a expiration of cache?
418   (unless (mixi-friend-realize-p friend)
419     (let (name)
420       (with-mixi-retrieve (mixi-friend-page friend)
421         (if (string-match mixi-friend-nick-regexp buffer)
422             (mixi-friend-set-nick friend (match-string 1 buffer))
423           (signal 'error (list 'cannot-find-nick friend)))
424         (when (string-match mixi-friend-name-regexp buffer)
425           (setq name (match-string 1 buffer))))
426       ;; For getting my name.
427       (unless name
428         (with-mixi-retrieve "/show_profile.pl"
429           (if (string-match mixi-my-name-regexp buffer)
430               (setq name (match-string 1 buffer))
431             (signal 'error (list 'cannot-find-name friend)))))
432       (mixi-friend-set-name friend name))
433     (mixi-friend-touch friend)))
434
435 (defun mixi-friend-realize-p (friend)
436   "Return the timestamp of FRIEND."
437   (unless (mixi-friend-p friend)
438     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
439   (aref (cdr friend) 0))
440
441 (defun mixi-friend-id (friend)
442   "Return the id of FRIEND."
443   (unless (mixi-friend-p friend)
444     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
445   (aref (cdr friend) 1))
446
447 (defun mixi-friend-nick (friend)
448   "Return the nick of FRIEND."
449   (unless (mixi-friend-p friend)
450     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
451   (unless (aref (cdr friend) 2)
452     (mixi-friend-realize friend))
453   (aref (cdr friend) 2))
454
455 (defun mixi-friend-name (friend)
456   "Return the name of FRIEND."
457   (unless (mixi-friend-p friend)
458     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
459   (mixi-friend-realize friend)
460   (aref (cdr friend) 3))
461
462 (defun mixi-friend-touch (friend)
463   "Set the timestamp of FRIEND."
464   (unless (mixi-friend-p friend)
465     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
466   (aset (cdr friend) 0 (current-time)))
467
468 (defun mixi-friend-set-nick (friend nick)
469   "Set the nick of FRIEND."
470   (unless (mixi-friend-p friend)
471     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
472   (aset (cdr friend) 2 nick))
473
474 (defun mixi-friend-set-name (friend name)
475   "Set the name of FRIEND."
476   (unless (mixi-friend-p friend)
477     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
478   (aset (cdr friend) 3 name))
479
480 (defmacro mixi-friend-list-page (&optional friend)
481   `(concat "/list_friend.pl?page=%d"
482            (when ,friend (concat "&id=" (number-to-string
483                                          (mixi-friend-id ,friend))))))
484
485 (defconst mixi-friend-list-id-regexp
486   "<a href=show_friend\\.pl\\?id=\\([0-9]+\\)")
487 (defconst mixi-friend-list-nick-regexp
488   "<td valign=middle>\\(.+\\)¤µ¤ó([0-9]+)<br />")
489
490 (defun mixi-get-friends (&optional friend)
491   "Get friends of FRIEND."
492   (unless (or (null friend) (mixi-friend-p friend))
493     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
494   (let ((ids (mixi-get-matched-items (mixi-friend-list-page friend)
495                                      mixi-friend-max-pages
496                                      mixi-friend-list-id-regexp))
497         (nicks (mixi-get-matched-items (mixi-friend-list-page friend)
498                                        mixi-friend-max-pages
499                                        mixi-friend-list-nick-regexp)))
500     (let ((index 0)
501           ret)
502       (while (< index (length ids))
503         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
504                                           (nth 0 (nth index nicks))) ret))
505         (incf index))
506       (reverse ret))))
507
508 ;; Favorite.
509 (defmacro mixi-favorite-list-page ()
510   `(concat "/list_bookmark.pl?page=%d"))
511
512 (defconst mixi-favorite-list-id-regexp
513   "<td ALIGN=center BGCOLOR=#FDF9F2 width=330><a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">")
514 (defconst mixi-favorite-list-nick-regexp
515   "<td BGCOLOR=#FDF9F2><font COLOR=#996600>̾&nbsp;&nbsp;Á°</font></td>
516 <td COLSPAN=2 BGCOLOR=#FFFFFF>\\(.+\\)</td></tr>")
517
518 (defun mixi-get-favorites ()
519   "Get favorites."
520   (let ((ids (mixi-get-matched-items (mixi-favorite-list-page)
521                                      mixi-favorite-max-pages
522                                      mixi-favorite-list-id-regexp))
523         (nicks (mixi-get-matched-items (mixi-favorite-list-page)
524                                        mixi-favorite-max-pages
525                                        mixi-favorite-list-nick-regexp)))
526     (let ((index 0)
527           ret)
528       (while (< index (length ids))
529         (setq ret (cons (mixi-make-friend (nth 0 (nth index ids))
530                                           (nth 0 (nth index nicks))) ret))
531         (incf index))
532       (reverse ret))))
533
534 ;; Log object.
535 (defun mixi-make-log (friend time)
536   "Return a log object."
537   (cons 'mixi-log (vector friend time)))
538
539 (defmacro mixi-log-p (log)
540   `(eq (mixi-object-class ,log) 'mixi-log))
541
542 (defun mixi-log-friend (log)
543   "Return the friend of LOG."
544   (unless (mixi-log-p log)
545     (signal 'wrong-type-argument (list 'mixi-log-p log)))
546   (aref (cdr log) 0))
547
548 (defun mixi-log-time (log)
549   "Return the time of LOG."
550   (unless (mixi-log-p log)
551     (signal 'wrong-type-argument (list 'mixi-log-p log)))
552   (aref (cdr log) 1))
553
554 (defmacro mixi-log-list-page ()
555   `(concat "/show_log.pl"))
556
557 (defconst mixi-log-list-regexp
558   "\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\) <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a><br>")
559
560 (defun mixi-get-logs ()
561   "Get logs."
562   (let ((items (mixi-get-matched-items (mixi-log-list-page)
563                                        mixi-log-max-pages
564                                        mixi-log-list-regexp)))
565     (mapcar (lambda (item)
566               (mixi-make-log (mixi-make-friend (nth 5 item) (nth 6 item))
567                              (encode-time 0 (nth 4 item) (nth 3 item)
568                                           (nth 2 item) (nth 1 item)
569                                           (nth 0 item))))
570             items)))
571
572 ;; Diary object.
573 (defvar mixi-diary-cache (make-hash-table :test 'equal))
574 (defun mixi-make-diary (owner id)
575   "Return a diary object."
576   (let ((owner (or owner (mixi-make-me))))
577     (mixi-make-cache (list (mixi-friend-id owner) id)
578                      (cons 'mixi-diary (vector nil owner id nil nil nil))
579                      mixi-diary-cache)))
580
581 (defmacro mixi-diary-p (diary)
582   `(eq (mixi-object-class ,diary) 'mixi-diary))
583
584 (defmacro mixi-diary-page (diary)
585   `(concat "/view_diary.pl?id=" (number-to-string (mixi-diary-id ,diary))
586            "&owner_id=" (number-to-string (mixi-friend-id
587                                            (mixi-diary-owner ,diary)))))
588
589 ;; FIXME: Remove `¤µ¤ó'.
590 (defconst mixi-diary-owner-nick-regexp
591   "<td WIDTH=490 background=http://img\\.mixi\\.jp/img/bg_w\\.gif><b><font COLOR=#605048>\\(.+\\)\\(¤µ¤ó\\)?¤ÎÆüµ­</font></b></td>")
592 (defconst mixi-diary-time-regexp
593   "<td ALIGN=center ROWSPAN=2 NOWRAP WIDTH=95 bgcolor=#FFD8B0>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
594 (defconst mixi-diary-title-regexp
595   "<td BGCOLOR=#FFF4E0 WIDTH=430>&nbsp;\\([^<]+\\)</td></tr>")
596 (defconst mixi-diary-content-regexp
597   "<td CLASS=h12>\\(.+\\)</td></tr>")
598
599 (defun mixi-diary-realize (diary)
600   "Realize a DIARY."
601   ;; FIXME: Check a expiration of cache?
602   (unless (mixi-diary-realize-p diary)
603     (with-mixi-retrieve (mixi-diary-page diary)
604       (if (string-match mixi-diary-owner-nick-regexp buffer)
605           (mixi-friend-set-nick (mixi-diary-owner diary)
606                                 (match-string 1 buffer))
607         (signal 'error (list 'cannot-find-owner-nick diary)))
608       (if (string-match mixi-diary-time-regexp buffer)
609           (mixi-diary-set-time
610            diary (encode-time 0 (string-to-number (match-string 5 buffer))
611                               (string-to-number (match-string 4 buffer))
612                               (string-to-number (match-string 3 buffer))
613                               (string-to-number (match-string 2 buffer))
614                               (string-to-number (match-string 1 buffer))))
615         (signal 'error (list 'cannot-find-time diary)))
616       (if (string-match mixi-diary-title-regexp buffer)
617           (mixi-diary-set-title diary (match-string 1 buffer))
618         (signal 'error (list 'cannot-find-title diary)))
619       (if (string-match mixi-diary-content-regexp buffer)
620           (mixi-diary-set-content diary (mixi-remove-markup
621                                          (match-string 1 buffer)))
622         (signal 'error (list 'cannot-find-content diary))))
623     (mixi-diary-touch diary)))
624
625 (defun mixi-diary-realize-p (diary)
626   "Return the timestamp of DIARY."
627   (unless (mixi-diary-p diary)
628     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
629   (aref (cdr diary) 0))
630
631 (defun mixi-diary-owner (diary)
632   "Return the owner of DIARY."
633   (unless (mixi-diary-p diary)
634     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
635   (aref (cdr diary) 1))
636
637 (defun mixi-diary-id (diary)
638   "Return the id of DIARY."
639   (unless (mixi-diary-p diary)
640     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
641   (aref (cdr diary) 2))
642
643 (defun mixi-diary-time (diary)
644   "Return the time of DIARY."
645   (unless (mixi-diary-p diary)
646     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
647   (mixi-diary-realize diary)
648   (aref (cdr diary) 3))
649
650 (defun mixi-diary-title (diary)
651   "Return the title of DIARY."
652   (unless (mixi-diary-p diary)
653     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
654   (mixi-diary-realize diary)
655   (aref (cdr diary) 4))
656
657 (defun mixi-diary-content (diary)
658   "Return the content of DIARY."
659   (unless (mixi-diary-p diary)
660     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
661   (mixi-diary-realize diary)
662   (aref (cdr diary) 5))
663
664 (defun mixi-diary-touch (diary)
665   "Set the timestamp of DIARY."
666   (unless (mixi-diary-p diary)
667     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
668   (aset (cdr diary) 0 (current-time)))
669
670 (defun mixi-diary-set-time (diary time)
671   "Set the time of DIARY."
672   (unless (mixi-diary-p diary)
673     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
674   (aset (cdr diary) 3 time))
675
676 (defun mixi-diary-set-title (diary title)
677   "Set the title of DIARY."
678   (unless (mixi-diary-p diary)
679     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
680   (aset (cdr diary) 4 title))
681
682 (defun mixi-diary-set-content (diary content)
683   "Set the content of DIARY."
684   (unless (mixi-diary-p diary)
685     (signal 'wrong-type-argument (list 'mixi-diary-p diary)))
686   (aset (cdr diary) 5 content))
687
688 (defmacro mixi-diary-list-page (&optional friend)
689   `(concat "/list_diary.pl?page=%d"
690            (when ,friend (concat "&id=" (number-to-string
691                                          (mixi-friend-id ,friend))))))
692
693 (defconst mixi-diary-list-regexp
694   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=[0-9]+\">")
695
696 (defun mixi-get-diaries (&optional friend)
697   "Get diaries of FRIEND."
698   (unless (or (null friend) (mixi-friend-p friend))
699     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
700   (let ((items (mixi-get-matched-items (mixi-diary-list-page friend)
701                                        mixi-diary-max-pages
702                                        mixi-diary-list-regexp)))
703     (mapcar (lambda (item)
704               (mixi-make-diary friend (nth 0 item)))
705             items)))
706
707 (defmacro mixi-new-diary-list-page ()
708   `(concat "/new_friend_diary.pl?page=%d"))
709
710 (defconst mixi-new-diary-list-regexp
711   "<a class=\"new_link\" href=view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)>")
712
713 (defun mixi-get-new-diaries ()
714   "Get new diaries."
715   (let ((items (mixi-get-matched-items (mixi-new-diary-list-page)
716                                        mixi-new-diary-max-pages
717                                        mixi-new-diary-list-regexp)))
718     (mapcar (lambda (item)
719               (mixi-make-diary (mixi-make-friend (nth 1 item)) (nth 0 item)))
720             items)))
721
722 ;; Community object.
723 (defvar mixi-community-cache (make-hash-table :test 'equal))
724 (defun mixi-make-community (id &optional name)
725   "Return a community object."
726   (mixi-make-cache id (cons 'mixi-community (vector nil id name nil nil nil
727                                                     nil))
728                    mixi-community-cache))
729
730 (defmacro mixi-community-p (community)
731   `(eq (mixi-object-class ,community) 'mixi-community))
732
733 (defmacro mixi-community-page (community)
734   `(concat "/view_community.pl?id=" (number-to-string
735                                      (mixi-community-id ,community))))
736
737 (defconst mixi-community-nodata-regexp
738   "^¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
739 (defconst mixi-community-name-regexp
740   "<td WIDTH=345>\\(.*\\)</td></tr>")
741 (defconst mixi-community-birthday-regexp
742   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>³«ÀßÆü</font></td>\n<td>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü</td>")
743 ;; FIXME: Care when the owner has seceded.
744 (defconst mixi-community-owner-regexp
745   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>´ÉÍý¿Í</font></td>\n<td>\n\n<a href=\"\\(home\\.pl\\|show_friend\\.pl\\?id=\\([0-9]+\\)\\)\">\n\\(.+\\)</a>")
746 (defconst mixi-community-category-regexp
747   "<td BGCOLOR=#F2DDB7><font COLOR=#996600>¥«¥Æ¥´¥ê</font></td>\n<td>\\([^<]+\\)</td>")
748 (defconst mixi-community-description-regexp
749   "<td CLASS=h120>\\(.+\\)</td>")
750
751 (defun mixi-community-realize (community)
752   "Realize a COMMUNITY."
753   ;; FIXME: Check a expiration of cache?
754   (unless (mixi-community-realize-p community)
755     (with-mixi-retrieve (mixi-community-page community)
756       (if (string-match mixi-community-nodata-regexp buffer)
757           ;; FIXME: Set all members?
758           (mixi-community-set-name community "¥Ç¡¼¥¿¤¬¤¢¤ê¤Þ¤»¤ó")
759         (if (string-match mixi-community-name-regexp buffer)
760             (mixi-community-set-name community (match-string 1 buffer))
761           (signal 'error (list 'cannot-find-name community)))
762         (if (string-match mixi-community-birthday-regexp buffer)
763             (mixi-community-set-birthday
764              community
765              (encode-time 0 0 0 (string-to-number (match-string 3 buffer))
766                           (string-to-number (match-string 2 buffer))
767                           (string-to-number (match-string 1 buffer))))
768           (signal 'error (list 'cannot-find-birthday community)))
769         (if (string-match mixi-community-owner-regexp buffer)
770             (if (string= (match-string 1 buffer) "home.pl")
771                 (mixi-community-set-owner community (mixi-make-me))
772               (mixi-community-set-owner
773                community (mixi-make-friend
774                           (string-to-number (match-string 2 buffer))
775                           (match-string 3 buffer))))
776           (signal 'error (list 'cannot-find-owner community)))
777         (if (string-match mixi-community-category-regexp buffer)
778             (mixi-community-set-category community (match-string 1 buffer))
779           (signal 'error (list 'cannot-find-category community)))
780         (if (string-match mixi-community-description-regexp buffer)
781             (mixi-community-set-description community (match-string 1 buffer))
782           (signal 'error (list 'cannot-find-description community)))))
783     (mixi-community-touch community)))
784
785 (defun mixi-community-realize-p (community)
786   "Return the timestamp of COMMUNITY."
787   (unless (mixi-community-p community)
788     (signal 'wrong-type-argument (list 'mixi-community-p community)))
789   (aref (cdr community) 0))
790
791 (defun mixi-community-id (community)
792   "Return the id of COMMUNITY."
793   (unless (mixi-community-p community)
794     (signal 'wrong-type-argument (list 'mixi-community-p community)))
795   (aref (cdr community) 1))
796
797 (defun mixi-community-name (community)
798   "Return the name of COMMUNITY."
799   (unless (mixi-community-p community)
800     (signal 'wrong-type-argument (list 'mixi-community-p community)))
801   (unless (aref (cdr community) 2)
802     (mixi-community-realize community))
803   (aref (cdr community) 2))
804
805 (defun mixi-community-birthday (community)
806   "Return the birthday of COMMUNITY."
807   (unless (mixi-community-p community)
808     (signal 'wrong-type-argument (list 'mixi-community-p community)))
809   (mixi-community-realize community)
810   (aref (cdr community) 3))
811
812 (defun mixi-community-owner (community)
813   "Return the owner of COMMUNITY."
814   (unless (mixi-community-p community)
815     (signal 'wrong-type-argument (list 'mixi-community-p community)))
816   (mixi-community-realize community)
817   (aref (cdr community) 4))
818
819 (defun mixi-community-category (community)
820   "Return the category of COMMUNITY."
821   (unless (mixi-community-p community)
822     (signal 'wrong-type-argument (list 'mixi-community-p community)))
823   (mixi-community-realize community)
824   (aref (cdr community) 5))
825
826 (defun mixi-community-description (community)
827   "Return the description of COMMUNITY."
828   (unless (mixi-community-p community)
829     (signal 'wrong-type-argument (list 'mixi-community-p community)))
830   (mixi-community-realize community)
831   (aref (cdr community) 6))
832
833 (defun mixi-community-touch (community)
834   "Set the timestamp of COMMUNITY."
835   (unless (mixi-community-p community)
836     (signal 'wrong-type-argument (list 'mixi-community-p community)))
837   (aset (cdr community) 0 (current-time)))
838
839 (defun mixi-community-set-name (community name)
840   "Set the name of COMMUNITY."
841   (unless (mixi-community-p community)
842     (signal 'wrong-type-argument (list 'mixi-community-p community)))
843   (aset (cdr community) 2 name))
844
845 (defun mixi-community-set-birthday (community birthday)
846   "Set the birthday of COMMUNITY."
847   (unless (mixi-community-p community)
848     (signal 'wrong-type-argument (list 'mixi-community-p community)))
849   (aset (cdr community) 3 birthday))
850
851 (defun mixi-community-set-owner (community owner)
852   "Set the owner of COMMUNITY."
853   (unless (mixi-community-p community)
854     (signal 'wrong-type-argument (list 'mixi-community-p community)))
855   (unless (mixi-friend-p owner)
856     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
857   (aset (cdr community) 4 owner))
858
859 (defun mixi-community-set-category (community category)
860   "Set the category of COMMUNITY."
861   (unless (mixi-community-p community)
862     (signal 'wrong-type-argument (list 'mixi-community-p community)))
863   (aset (cdr community) 5 category))
864
865 (defun mixi-community-set-description (community description)
866   "Set the name of COMMUNITY."
867   (unless (mixi-community-p community)
868     (signal 'wrong-type-argument (list 'mixi-community-p community)))
869   (aset (cdr community) 6 description))
870
871 (defmacro mixi-community-list-page (&optional friend)
872   `(concat "/list_community.pl?page=%d"
873            (when ,friend (concat "&id=" (number-to-string
874                                          (mixi-friend-id ,friend))))))
875
876 (defconst mixi-community-list-id-regexp
877   "<a href=view_community\\.pl\\?id=\\([0-9]+\\)")
878 (defconst mixi-community-list-name-regexp
879   "<td valign=middle>\\(.+\\)([0-9]+)</td>")
880
881 (defun mixi-get-communities (&optional friend)
882   "Get communities of FRIEND."
883   (unless (or (null friend) (mixi-friend-p friend))
884     (signal 'wrong-type-argument (list 'mixi-friend-p friend)))
885   (let ((ids (mixi-get-matched-items (mixi-community-list-page friend)
886                                      mixi-community-max-pages
887                                      mixi-community-list-id-regexp))
888         (names (mixi-get-matched-items (mixi-community-list-page friend)
889                                      mixi-community-max-pages
890                                      mixi-community-list-name-regexp)))
891     (let ((index 0)
892           ret)
893       (while (< index (length ids))
894         (setq ret (cons (mixi-make-community (nth 0 (nth index ids))
895                                              (nth 0 (nth index names))) ret))
896         (incf index))
897       (reverse ret))))
898
899 ;; Topic object.
900 (defvar mixi-topic-cache (make-hash-table :test 'equal))
901 (defun mixi-make-topic (community id)
902   "Return a topic object."
903   (mixi-make-cache (list (mixi-community-id community) id)
904                    (cons 'mixi-topic (vector nil community id nil nil nil nil))
905                    mixi-topic-cache))
906
907 (defmacro mixi-topic-p (topic)
908   `(eq (mixi-object-class ,topic) 'mixi-topic))
909
910 (defmacro mixi-topic-page (topic)
911   `(concat "/view_bbs.pl?id=" (number-to-string (mixi-topic-id ,topic))
912            "&comm_id=" (number-to-string
913                         (mixi-community-id (mixi-topic-community ,topic)))))
914
915 (defconst mixi-topic-time-regexp
916   "<td rowspan=\"3\" width=\"110\" bgcolor=\"#ffd8b0\" align=\"center\" valign=\"top\" nowrap>\\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)</td>")
917 (defconst mixi-topic-title-regexp
918   "<td bgcolor=\"#fff4e0\">&nbsp;\\([^<]+\\)</td>")
919 ;; FIXME: Remove `¤µ¤ó'.
920 (defconst mixi-topic-owner-regexp
921   "<td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#dfb479\"></font>&nbsp;<a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)\\(¤µ¤ó\\)?</a>")
922 (defconst mixi-topic-content-regexp
923   "<td class=\"h120\"><table><tr>\\(.+\\)?</tr></table>\\(.+\\)</td>")
924
925 (defun mixi-topic-realize (topic)
926   "Realize a TOPIC."
927   ;; FIXME: Check a expiration of cache?
928   (unless (mixi-topic-realize-p topic)
929     (with-mixi-retrieve (mixi-topic-page topic)
930       (if (string-match mixi-topic-time-regexp buffer)
931           (mixi-topic-set-time
932            topic (encode-time 0 (string-to-number (match-string 5 buffer))
933                               (string-to-number (match-string 4 buffer))
934                               (string-to-number (match-string 3 buffer))
935                               (string-to-number (match-string 2 buffer))
936                               (string-to-number (match-string 1 buffer))))
937         (signal 'error (list 'cannot-find-time topic)))
938       (if (string-match mixi-topic-title-regexp buffer)
939           (mixi-topic-set-title topic (match-string 1 buffer))
940         (signal 'error (list 'cannot-find-title topic)))
941       (if (string-match mixi-topic-owner-regexp buffer)
942           (mixi-topic-set-owner topic
943                                 (mixi-make-friend
944                                  (string-to-number (match-string 1 buffer))
945                                  (match-string 2 buffer)))
946         (signal 'error (list 'cannot-find-owner topic)))
947       (if (string-match mixi-topic-content-regexp buffer)
948           (mixi-topic-set-content topic (mixi-remove-markup
949                                          (match-string 2 buffer)))
950         (signal 'error (list 'cannot-find-content topic))))
951     (mixi-topic-touch topic)))
952
953 (defun mixi-topic-realize-p (topic)
954   "Return the timestamp of TOPIC."
955   (unless (mixi-topic-p topic)
956     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
957   (aref (cdr topic) 0))
958
959 (defun mixi-topic-community (topic)
960   "Return the community of TOPIC."
961   (unless (mixi-topic-p topic)
962     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
963   (aref (cdr topic) 1))
964
965 (defun mixi-topic-id (topic)
966   "Return the id of TOPIC."
967   (unless (mixi-topic-p topic)
968     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
969   (aref (cdr topic) 2))
970
971 (defun mixi-topic-time (topic)
972   "Return the time of TOPIC."
973   (unless (mixi-topic-p topic)
974     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
975   (mixi-topic-realize topic)
976   (aref (cdr topic) 3))
977
978 (defun mixi-topic-title (topic)
979   "Return the title of TOPIC."
980   (unless (mixi-topic-p topic)
981     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
982   (mixi-topic-realize topic)
983   (aref (cdr topic) 4))
984
985 (defun mixi-topic-owner (topic)
986   "Return the owner of TOPIC."
987   (unless (mixi-topic-p topic)
988     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
989   (mixi-topic-realize topic)
990   (aref (cdr topic) 5))
991
992 (defun mixi-topic-content (topic)
993   "Return the content of TOPIC."
994   (unless (mixi-topic-p topic)
995     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
996   (mixi-topic-realize topic)
997   (aref (cdr topic) 6))
998
999 (defun mixi-topic-touch (topic)
1000   "Set the timestamp of TOPIC."
1001   (unless (mixi-topic-p topic)
1002     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1003   (aset (cdr topic) 0 (current-time)))
1004
1005 (defun mixi-topic-set-time (topic time)
1006   "Set the time of TOPIC."
1007   (unless (mixi-topic-p topic)
1008     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1009   (aset (cdr topic) 3 time))
1010
1011 (defun mixi-topic-set-title (topic title)
1012   "Set the title of TOPIC."
1013   (unless (mixi-topic-p topic)
1014     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1015   (aset (cdr topic) 4 title))
1016
1017 (defun mixi-topic-set-owner (topic owner)
1018   "Set the owner of TOPIC."
1019   (unless (mixi-topic-p topic)
1020     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1021   (unless (mixi-friend-p owner)
1022     (signal 'wrong-type-argument (list 'mixi-friend-p owner)))
1023   (aset (cdr topic) 5 owner))
1024
1025 (defun mixi-topic-set-content (topic content)
1026   "Set the content of TOPIC."
1027   (unless (mixi-topic-p topic)
1028     (signal 'wrong-type-argument (list 'mixi-topic-p topic)))
1029   (aset (cdr topic) 6 content))
1030
1031 (defmacro mixi-topic-list-page (community)
1032   `(concat "/list_bbs.pl?page=%d"
1033            "&id=" (number-to-string (mixi-community-id ,community))))
1034
1035 (defconst mixi-topic-list-regexp
1036   "<a href=view_bbs\\.pl\\?id=\\([0-9]+\\)")
1037
1038 (defun mixi-get-topics (community)
1039   "Get topics of COMMUNITY."
1040   (unless (mixi-community-p community)
1041     (signal 'wrong-type-argument (list 'mixi-community-p community)))
1042   (let ((items (mixi-get-matched-items (mixi-topic-list-page community)
1043                                        mixi-topic-max-pages
1044                                        mixi-topic-list-regexp)))
1045     (mapcar (lambda (item)
1046               (mixi-make-topic community (nth 0 item)))
1047             items)))
1048
1049 (defmacro mixi-new-topic-list-page ()
1050   `(concat "/new_bbs.pl?page=%d"))
1051
1052 (defconst mixi-new-topic-list-regexp
1053   "<a href=\"view_bbs\\.pl\\?id=\\([0-9]+\\)&comment_count=[0-9]+&comm_id=\\([0-9]+\\)\" class=\"new_link\">")
1054
1055 (defun mixi-get-new-topics ()
1056   "Get new topics."
1057   (let ((items (mixi-get-matched-items (mixi-new-topic-list-page)
1058                                        mixi-new-topic-max-pages
1059                                        mixi-new-topic-list-regexp)))
1060     (mapcar (lambda (item)
1061               (mixi-make-topic (mixi-make-community (nth 1 item))
1062                                (nth 0 item)))
1063             items)))
1064
1065 ;; Comment object.
1066 (defun mixi-make-comment (parent owner time content)
1067   "Return a comment object."
1068   (cons 'mixi-comment (vector parent owner time content)))
1069
1070 (defmacro mixi-comment-p (comment)
1071   `(eq (mixi-object-class ,comment) 'mixi-comment))
1072
1073 (defun mixi-comment-parent (comment)
1074   "Return the parent of COMMENT."
1075   (unless (mixi-comment-p comment)
1076     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1077   (aref (cdr comment) 0))
1078
1079 (defun mixi-comment-owner (comment)
1080   "Return the owner of COMMENT."
1081   (unless (mixi-comment-p comment)
1082     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1083   (aref (cdr comment) 1))
1084
1085 (defun mixi-comment-time (comment)
1086   "Return the time of COMMENT."
1087   (unless (mixi-comment-p comment)
1088     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1089   (aref (cdr comment) 2))
1090
1091 (defun mixi-comment-content (comment)
1092   "Return the content of COMMENT."
1093   (unless (mixi-comment-p comment)
1094     (signal 'wrong-type-argument (list 'mixi-comment-p comment)))
1095   (aref (cdr comment) 3))
1096
1097 (defun mixi-diary-comment-list-page (diary)
1098   (concat "/view_diary.pl?page=all"
1099           "&id=" (number-to-string (mixi-diary-id diary))
1100           "&owner_id=" (number-to-string
1101                         (mixi-friend-id (mixi-diary-owner diary)))))
1102
1103 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1104 (defconst mixi-diary-comment-list-regexp
1105 "<td rowspan=\"2\" align=\"center\" width=\"95\" bgcolor=\"#f2ddb7\" nowrap>
1106 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>\\([0-9]+\\):\\([0-9]+\\)\\(<br>
1107 <input type=checkbox name=comment_id value=\".+\">
1108 \\|\\)
1109 </td>
1110 <td ALIGN=center BGCOLOR=#FDF9F2 WIDTH=430>
1111 <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"410\">
1112 <tr>
1113 <td>
1114 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1115
1116 </td>
1117 </tr>
1118 </table>
1119 </td>
1120 </tr>
1121 <!-- ËÜʸ : start -->
1122 <tr>
1123 <td bgcolor=\"#ffffff\">
1124 <table BORDER=0 CELLSPACING=0 CELLPADDING=[35] WIDTH=410>
1125 <tr>
1126 <td CLASS=h12>
1127 \\(.+\\)
1128 </td></tr></table>")
1129
1130 (defun mixi-topic-comment-list-page (topic)
1131   (concat "/view_bbs.pl?page=all"
1132           "&id=" (number-to-string (mixi-topic-id topic))
1133           "&comm_id=" (number-to-string
1134                         (mixi-community-id (mixi-topic-community topic)))))
1135
1136 ;; FIXME: Split regexp to time, owner(id and nick) and contents.
1137 (defconst mixi-topic-comment-list-regexp
1138   "<tr valign=\"top\">
1139 <td rowspan=\"2\" width=\"110\" bgcolor=\"#f2ddb7\" align=\"center\" nowrap>
1140 \\([0-9]+\\)ǯ\\([0-9]+\\)·î\\([0-9]+\\)Æü<br>
1141 \\([0-9]+\\):\\([0-9]+\\)<br>
1142 \\(</td>\\)
1143 <td bgcolor=\"#fdf9f2\">&nbsp;<font color=\"#f8a448\">
1144 <b>&nbsp;&nbsp;[0-9]+</b>:</font>&nbsp;
1145
1146 <a href=\"show_friend\\.pl\\?id=\\([0-9]+\\)\">\\(.+\\)</a>
1147
1148
1149
1150 </td>
1151 </tr>
1152 <tr>
1153 <td bgcolor=\"#ffffff\" align=\"center\">
1154 <table border=\"0\" cellspacing=\"0\" cellpadding=\"5\" width=\"500\">
1155 <tr>
1156 <td class=\"h120\">
1157
1158 \\(.+\\)
1159 </td>
1160 </tr>
1161 </table>
1162 </td>
1163 </tr>")
1164
1165 (defun mixi-get-comments (parent)
1166   "Get comments of PARENT."
1167   (unless (mixi-object-p parent)
1168     (signal 'wrong-type-argument (list 'mixi-object-p object)))
1169   (let* ((name (mixi-object-name parent))
1170          (list-page (intern (concat mixi-object-prefix name
1171                                     "-comment-list-page")))
1172          (regexp (eval (intern (concat mixi-object-prefix name
1173                                        "-comment-list-regexp")))))
1174     (let ((items (mixi-get-matched-items (funcall list-page parent) 1 regexp)))
1175       (mapcar (lambda (item)
1176                 (mixi-make-comment parent (mixi-make-friend
1177                                            (nth 6 item) (nth 7 item))
1178                                    (encode-time 0 (nth 4 item)
1179                                                 (nth 3 item)
1180                                                 (nth 2 item)
1181                                                 (nth 1 item)
1182                                                 (nth 0 item))
1183                                    (mixi-remove-markup (nth 8 item))))
1184               items))))
1185
1186 (defmacro mixi-new-comment-list-page ()
1187   `(concat "/new_comment.pl?page=%d"))
1188
1189 (defconst mixi-new-comment-list-regexp
1190   "<a href=\"view_diary\\.pl\\?id=\\([0-9]+\\)&owner_id=\\([0-9]+\\)&comment_count=[0-9]+\" class=\"new_link\">")
1191
1192 (defun mixi-get-new-comments ()
1193   "Get new comments."
1194   (let ((items (mixi-get-matched-items (mixi-new-comment-list-page)
1195                                        mixi-new-comment-max-pages
1196                                        mixi-new-comment-list-regexp)))
1197     (mapcar (lambda (item)
1198               (let ((diary (mixi-make-diary
1199                             (mixi-make-friend (nth 1 item))
1200                             (nth 0 item))))
1201                 (mixi-get-comments diary)))
1202             items)))
1203
1204 ;;
1205
1206 ;; FIXME: Move to the class method.
1207
1208 ;; FIXME: When it got some results, this function doesn't work fine.
1209 (defun mixi-friend-to-id (friend)
1210   (with-mixi-retrieve (concat "/search.pl?submit=main&nickname="
1211                               (w3m-url-encode-string friend
1212                                                      mixi-coding-system))
1213     (when (string-match "show_friend\\.pl\\?id=\\([0-9]+\\)" buffer)
1214       (string-to-number (match-string 1 buffer)))))
1215
1216 ;; FIXME: When it got some results, this function doesn't work fine.
1217 (defun mixi-community-to-id (community)
1218   (with-mixi-retrieve (concat "/search_community.pl?sort="
1219                               "&type=com&submit=main&keyword="
1220                               (w3m-url-encode-string community
1221                                                      mixi-coding-system))
1222     (when (string-match "view_community\\.pl\\?id=\\([0-9]+\\)" buffer)
1223       (string-to-number (match-string 1 buffer)))))
1224
1225 (provide 'mixi)
1226
1227 ;;; mixi.el ends here