+;; News object.
+(defvar mixi-news-cache (make-hash-table :test 'equal))
+(defun mixi-make-news (media-id id &optional media time title content)
+ "Return a news object."
+ (mixi-make-cache (list media-id id)
+ (cons 'mixi-news (vector nil media-id id media time title
+ content))
+ mixi-news-cache))
+
+(defconst mixi-news-url-regexp
+ "/view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)")
+
+(defun mixi-make-news-from-url (url)
+ "Return a news object from URL."
+ (when (string-match mixi-news-url-regexp url)
+ (let ((id (match-string 1 url))
+ (media-id (match-string 2 url)))
+ (mixi-make-news media-id id))))
+
+(defmacro mixi-news-p (news)
+ `(eq (mixi-object-class ,news) 'mixi-news))
+
+(defmacro mixi-news-page (news)
+ `(concat "http://news.mixi.jp/view_news.pl?id=" (mixi-news-id ,news)
+ "&media_id=" (mixi-news-media-id ,news)))
+
+(defconst mixi-news-finished-regexp
+ "¿½¤·Ìõ¤´¤¶¤¤¤Þ¤»¤ó¤¬¡¢¤³¤Î¥Ë¥å¡¼¥¹¤Ï·ÇºÜ¤¬½ªÎ»¤·¤¿¤«¡¢URL¤¬´Ö°ã¤Ã¤Æ¤¤¤¤¤ë¤¿¤á¤´Í÷¤¤¤¿¤À¤±¤Þ¤»¤ó¡£</td>")
+(defconst mixi-news-title-regexp
+ "<td HEIGHT=\"46\" STYLE=\"font-weight: bold;font-size: 14px;\" CLASS=\"h130\">\\(.+\\)\\(
+\\)?</td>")
+(defconst mixi-news-media-time-regexp
+ "<td COLSPAN=\"2\" ALIGN=\"right\">(\\(.+\\) - \\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\))</td></tr>")
+(defconst mixi-news-content-regexp
+ "<td CLASS=\"h150\">
+
+\\(.+\\)
+<br>
+\\(.*
+\\)?
+
+\\(</td>\\|<br>\\)")
+
+(defun mixi-realize-news (news)
+ "Realize a NEWS."
+ ;; FIXME: Check a expiration of cache?
+ (unless (mixi-object-realized-p news)
+ (with-mixi-retrieve (mixi-news-page news)
+ (if (re-search-forward mixi-news-finished-regexp nil t)
+ (mixi-news-set-content news (match-string 0))
+ (if (re-search-forward mixi-news-title-regexp nil t)
+ (mixi-news-set-title news (match-string 1))
+ (mixi-realization-error 'cannot-find-title news))
+ (if (re-search-forward mixi-news-media-time-regexp nil t)
+ (progn
+ (mixi-news-set-media news (match-string 1))
+ (let ((year (nth 5 (decode-time (current-time))))
+ (month (nth 4 (decode-time (current-time))))
+ (month-of-item (string-to-number (match-string 2))))
+ (when (> month-of-item month)
+ (decf year))
+ (mixi-news-set-time
+ news (encode-time 0 (string-to-number (match-string 5))
+ (string-to-number (match-string 4))
+ (string-to-number (match-string 3))
+ month year))))
+ (mixi-realization-error 'cannot-find-media-time news))
+ (if (re-search-forward mixi-news-content-regexp nil t)
+ (mixi-news-set-content news (match-string 1))
+ (mixi-realization-error 'cannot-find-content news))))
+ (mixi-object-touch news)))
+
+(defun mixi-news-media-id (news)
+ "Return the media-id of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aref (cdr news) 1))
+
+(defun mixi-news-id (news)
+ "Return the id of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aref (cdr news) 2))
+
+(defun mixi-news-media (news)
+ "Return the media of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (unless (aref (cdr news) 3)
+ (mixi-realize-news news))
+ (aref (cdr news) 3))
+
+(defun mixi-news-time (news)
+ "Return the time of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (unless (aref (cdr news) 4)
+ (mixi-realize-news news))
+ (aref (cdr news) 4))
+
+(defun mixi-news-title (news)
+ "Return the title of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (unless (aref (cdr news) 5)
+ (mixi-realize-news news))
+ (aref (cdr news) 5))
+
+(defun mixi-news-content (news)
+ "Return the content of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (mixi-realize-news news)
+ (aref (cdr news) 6))
+
+(defun mixi-news-set-media (news media)
+ "Set the media of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aset (cdr news) 3 media))
+
+(defun mixi-news-set-time (news time)
+ "Set the time of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aset (cdr news) 4 time))
+
+(defun mixi-news-set-title (news title)
+ "Set the title of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aset (cdr news) 5 title))
+
+(defun mixi-news-set-content (news content)
+ "Set the content of NEWS."
+ (unless (mixi-news-p news)
+ (signal 'wrong-type-argument (list 'mixi-news-p news)))
+ (aset (cdr news) 6 content))
+
+(defconst mixi-news-category-list '(domestic politics economy area abroad
+ sports entertainment IT))
+
+(defmacro mixi-news-category-p (category)
+ `(memq ,category mixi-news-category-list))
+
+(defun mixi-news-category-id (category)
+ "Return the id of CATEGORY."
+ (unless (mixi-news-category-p category)
+ (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
+ (number-to-string
+ (1+ (- (length mixi-news-category-list)
+ (length (memq category mixi-news-category-list))))))
+
+(defconst mixi-news-sort-list '(newest pickup))
+
+(defmacro mixi-news-sort-p (sort)
+ `(memq ,sort mixi-news-sort-list))
+
+(defun mixi-news-sort-id (sort)
+ "Return the id of SORT."
+ (unless (mixi-news-sort-p sort)
+ (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
+ (number-to-string
+ (- (length mixi-news-sort-list)
+ (length (memq sort mixi-news-sort-list)))))
+
+(defmacro mixi-news-list-page (category sort)
+ `(concat "http://news.mixi.jp/list_news_category.pl?page=%d"
+ "&sort=" (mixi-news-sort-id ,sort)
+ "&id=" (mixi-news-category-id ,category)
+ "&type=bn"))
+
+(defconst mixi-news-list-regexp
+ "<tr bgcolor=\"\\(#FCF5EB\\|#FFFFFF\\)\">
+<td WIDTH=\"1%\" valign=top CLASS=\"h120\">¡¦</td>
+<td WIDTH=\"97%\" CLASS=\"h120\"><A HREF=\"view_news\\.pl\\?id=\\([0-9]+\\)&media_id=\\([0-9]+\\)\"class=\"new_link\">\\(.+\\)</A>
+\\(<IMG SRC=\"http://img\\.mixi\\.jp/img/news_camera3\\.gif\" WIDTH=\"11\" HEIGHT=\"12\">\\|\\)
+
+</td>
+<td WIDTH=\"1%\" nowrap CLASS=\"f08\"><A HREF=\"list_news_media\\.pl\\?id=[0-9]+\">\\(.+\\)</A></td>
+<td WIDTH=\"1%\" nowrap CLASS=\"f08\">\\([0-9]+\\)·î\\([0-9]+\\)Æü \\([0-9]+\\):\\([0-9]+\\)</td></tr>")
+
+;;;###autoload
+(defun mixi-get-news (category sort &optional range)
+ "Get news of CATEGORY and SORT."
+ (unless (mixi-news-category-p category)
+ (signal 'wrong-type-argument (list 'mixi-news-category-p category)))
+ (unless (mixi-news-sort-p sort)
+ (signal 'wrong-type-argument (list 'mixi-news-sort-p sort)))
+ (let ((items (mixi-get-matched-items (mixi-news-list-page category sort)
+ mixi-news-list-regexp
+ range))
+ (year (nth 5 (decode-time (current-time))))
+ (month (nth 4 (decode-time (current-time)))))
+ (mapcar (lambda (item)
+ (let ((month-of-item (string-to-number (nth 6 item))))
+ (when (> month-of-item month)
+ (decf year))
+ (setq month month-of-item)
+ (mixi-make-news (nth 2 item) (nth 1 item) (nth 5 item)
+ (encode-time
+ 0 (string-to-number (nth 9 item))
+ (string-to-number (nth 8 item))
+ (string-to-number (nth 7 item))
+ month year)
+ (nth 3 item))))
+ items)))
+