ed60a930d96b3f85d6e1d137d8f5f55b90e0ce6f
[elisp/gnus.git-] / lisp / nnshimbun.el
1 ;;; -*- mode: Emacs-Lisp; coding: junet -*-
2
3 ;;; Author: TSUCHIYA Masatoshi <tsuchiya@pine.kuee.kyoto-u.ac.jp>
4 ;;; Keywords: news
5
6 ;;; Copyright:
7
8 ;; This file is a part of Semi-Gnus.
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.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; Gnus backend to read newspapers on WEB.
28
29
30 ;;; Defintinos:
31
32 (gnus-declare-backend "nnshimbun" 'address)
33
34 (eval-when-compile (require 'cl))
35
36 (require 'nnheader)
37 (require 'nnmail)
38 (require 'nnoo)
39 (require 'gnus-bcklg)
40 (eval-when-compile
41   (ignore-errors
42     (require 'nnweb)))
43 ;; Report failure to find w3 at load time if appropriate.
44 (eval '(require 'nnweb))
45
46
47 (nnoo-declare nnshimbun)
48
49 (defvar nnshimbun-check-interval 300)
50
51 (defvar nnshimbun-type-definition
52   `(("asahi"
53      (url . "http://spin.asahi.com/")
54      (groups "national" "business" "politics" "international" "sports" "personal" "feneral")
55      (coding-system  . ,(if (boundp 'MULE) '*sjis* 'shift_jis))
56      (generate-nov   . nnshimbun-generate-nov-for-each-group)
57      (get-headers    . nnshimbun-asahi-get-headers)
58      (index-url      . (format "%sp%s.html" nnshimbun-url nnshimbun-current-group))
59      (from-address   . "webmaster@www.asahi.com")
60      (make-contents  . nnshimbun-make-text-or-html-contents)
61      (contents-start . "\n<!-- Start of kiji -->\n")
62      (contents-end   . "\n<!-- End of kiji -->\n"))
63     ("sponichi"
64      (url . "http://www.sponichi.co.jp/")
65      (groups "baseball" "soccer" "usa" "others" "society" "entertainment" "horseracing")
66      (coding-system  . ,(if (boundp 'MULE) '*sjis* 'shift_jis))
67      (generate-nov   . nnshimbun-generate-nov-for-each-group)
68      (get-headers    . nnshimbun-sponichi-get-headers)
69      (index-url      . (format "%s%s/index.html" nnshimbun-url nnshimbun-current-group))
70      (from-address   . "webmaster@www.sponichi.co.jp")
71      (make-contents  . nnshimbun-make-text-or-html-contents)
72      (contents-start . "\n<span class=\"text\">\e$B!!\e(B")
73      (contents-end   . "\n"))
74     ("cnet"
75      (url . "http://cnet.sphere.ne.jp/")
76      (groups "comp")
77      (coding-system  . ,(if (boundp 'MULE) '*sjis* 'shift_jis))
78      (generate-nov   . nnshimbun-generate-nov-for-each-group)
79      (get-headers    . nnshimbun-cnet-get-headers)
80      (index-url      . (format "%s/News/Oneweek/" nnshimbun-url))
81      (from-address   . "cnet@sphere.ad.jp")
82      (make-contents  . nnshimbun-make-html-contents)
83      (contents-start . "\n<!--KIJI-->\n")
84      (contents-end   . "\n<!--/KIJI-->\n"))
85     ("wired"
86      (url . "http://www.hotwired.co.jp/")
87      (groups "business" "culture" "technology")
88      (coding-system  . ,(if (boundp 'MULE) '*euc-japan* 'euc-jp))
89      (generate-nov   . nnshimbun-generate-nov-for-all-groups)
90      (get-headers    . nnshimbun-wired-get-all-headers)
91      (index-url)
92      (from-address   . "webmaster@www.hotwired.co.jp")
93      (make-contents  . nnshimbun-make-html-contents)
94      (contents-start . "\n<!-- START_OF_BODY -->\n")
95      (contents-end   . "\n<!-- END_OF_BODY -->\n"))
96     ("yomiuri"
97      (url . "http://www.yomiuri.co.jp/")
98      (groups "shakai" "sports" "seiji" "keizai" "kokusai" "fuho")
99      (coding-system  . ,(if (boundp 'MULE) '*sjis* 'shift_jis))
100      (generate-nov   . nnshimbun-generate-nov-for-all-groups)
101      (get-headers    . nnshimbun-yomiuri-get-all-headers)
102      (index-url      . (concat nnshimbun-url "main.htm"))
103      (from-address   . "webmaster@www.yomiuri.co.jp")
104      (make-contents  . nnshimbun-make-text-or-html-contents)
105      (contents-start . "\n<!--  honbun start  -->\n")
106      (contents-end   . "\n<!--  honbun end  -->\n"))
107     ("zdnet"
108      (url . "http://zdseek.pub.softbank.co.jp/news/")
109      (groups "comp")
110      (coding-system  . ,(if (boundp 'MULE) '*sjis* 'shift_jis))
111      (generate-nov   . nnshimbun-generate-nov-for-each-group)
112      (get-headers    . nnshimbun-zdnet-get-headers)
113      (index-url      . nnshimbun-url)
114      (from-address   . "zdnn@softbank.co.jp")
115      (make-contents  . nnshimbun-make-html-contents)
116      (contents-start . "<!--BODY-->")
117      (contents-end   . "<!--BODYEND-->"))
118     ))
119
120 (defvar nnshimbun-x-face-alist
121   '(("default" .
122      (("default" .
123        "X-Face: Ygq$6P.,%Xt$U)DS)cRY@k$VkW!7(X'X'?U{{osjjFG\"E]hND;SPJ-J?O?R|a?L
124         g2$0rVng=O3Lt}?~IId8Jj&vP^3*o=LKUyk(`t%0c!;t6REk=JbpsEn9MrN7gZ%"))))
125   "Alist of server vs. alist of group vs. X-Face field.  It looks like:
126
127 \((\"asahi\" . ((\"national\" . \"X-face: ***\")
128              (\"business\" . \"X-Face: ***\")
129                 ;;
130                 ;;
131              (\"default\" . \"X-face: ***\")))
132  (\"sponichi\" . ((\"baseball\" . \"X-face: ***\")
133                 (\"soccer\" . \"X-Face: ***\")
134                 ;;
135                 ;;
136                 (\"default\" . \"X-face: ***\")))
137                 ;;
138  (\"default\" . ((\"default\" . \"X-face: ***\")))")
139
140 (defvoo nnshimbun-directory (nnheader-concat gnus-directory "shimbun/")
141   "Where nnshimbun will save its files.")
142
143 (defvoo nnshimbun-nov-is-evil nil
144   "*Non-nil means that nnshimbun will never retrieve NOV headers.")
145
146 (defvoo nnshimbun-nov-file-name ".overview")
147
148 (defvoo nnshimbun-pre-fetch-article nil
149   "*Non nil means that nnshimbun fetch unread articles when scanning groups.")
150
151 ;; set by nnshimbun-possibly-change-group
152 (defvoo nnshimbun-buffer nil)
153 (defvoo nnshimbun-current-directory nil)
154 (defvoo nnshimbun-current-group nil)
155
156 ;; set by nnshimbun-open-server
157 (defvoo nnshimbun-url nil)
158 (defvoo nnshimbun-coding-system nil)
159 (defvoo nnshimbun-groups nil)
160 (defvoo nnshimbun-generate-nov nil)
161 (defvoo nnshimbun-get-headers nil)
162 (defvoo nnshimbun-index-url nil)
163 (defvoo nnshimbun-from-address nil)
164 (defvoo nnshimbun-make-contents nil)
165 (defvoo nnshimbun-contents-start nil)
166 (defvoo nnshimbun-contents-end nil)
167 (defvoo nnshimbun-server-directory nil)
168
169 (defvoo nnshimbun-status-string "")
170 (defvoo nnshimbun-nov-last-check nil)
171 (defvoo nnshimbun-nov-buffer-alist nil)
172 (defvoo nnshimbun-nov-buffer-file-name nil)
173
174 (defvoo nnshimbun-keep-backlog 300)
175 (defvoo nnshimbun-backlog-articles nil)
176 (defvoo nnshimbun-backlog-hashtb nil)
177
178
179
180 ;;; backlog
181 (defmacro nnshimbun-backlog (&rest form)
182   `(let ((gnus-keep-backlog nnshimbun-keep-backlog)
183          (gnus-backlog-buffer (format " *nnshimbun backlog %s*" (nnoo-current-server 'nnshimbun)))
184          (gnus-backlog-articles nnshimbun-backlog-articles)
185          (gnus-backlog-hashtb nnshimbun-backlog-hashtb))
186      (unwind-protect
187          (progn ,@form)
188        (setq nnshimbun-backlog-articles gnus-backlog-articles
189              nnshimbun-backlog-hashtb gnus-backlog-hashtb))))
190 (put 'nnshimbun-backlog 'lisp-indent-function 0)
191 (put 'nnshimbun-backlog 'edebug-form-spec '(form body))
192
193
194
195 ;;; Interface Functions
196 (nnoo-define-basics nnshimbun)
197
198 (deffoo nnshimbun-open-server (server &optional defs)
199   ;; Set default values.
200   (dolist (default (cdr (assoc server nnshimbun-type-definition)))
201     (let ((symbol (intern (concat "nnshimbun-" (symbol-name (car default))))))
202       (unless (assq symbol defs)
203         (push (list symbol (cdr default)) defs))))
204   ;; Set directory for server working files.
205   (push (list 'nnshimbun-server-directory
206               (file-name-as-directory
207                (expand-file-name server nnshimbun-directory)))
208         defs)
209   (nnoo-change-server 'nnshimbun server defs)
210   (nnshimbun-possibly-change-group nil server)
211   ;; Make directories.
212   (unless (file-exists-p nnshimbun-directory)
213     (ignore-errors (make-directory nnshimbun-directory t)))
214   (cond
215    ((not (file-exists-p nnshimbun-directory))
216     (nnshimbun-close-server)
217     (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-directory))
218    ((not (file-directory-p (file-truename nnshimbun-directory)))
219     (nnshimbun-close-server)
220     (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-directory))
221    (t
222     (unless (file-exists-p nnshimbun-server-directory)
223       (ignore-errors (make-directory nnshimbun-server-directory t)))
224     (cond
225      ((not (file-exists-p nnshimbun-server-directory))
226       (nnshimbun-close-server)
227       (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-server-directory))
228      ((not (file-directory-p (file-truename nnshimbun-server-directory)))
229       (nnshimbun-close-server)
230       (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-server-directory))
231      (t
232       (nnheader-report 'nnshimbun "Opened server %s using directory %s"
233                        server nnshimbun-server-directory)
234       t)))))
235
236 (deffoo nnshimbun-close-server (&optional server)
237   (and (nnshimbun-server-opened server)
238        (gnus-buffer-live-p nnshimbun-buffer)
239        (kill-buffer nnshimbun-buffer))
240   (nnshimbun-backlog (gnus-backlog-shutdown))
241   (nnshimbun-save-nov)
242   (nnoo-close-server 'nnshimbun server)
243   t)
244
245 (defun nnshimbun-retrieve-url (url &optional no-cache)
246   "Rertrieve URL contents and insert to current buffer."
247   (let ((coding-system-for-read 'binary)
248         (coding-system-for-write 'binary))
249     (set-buffer-multibyte nil)
250     ;; Following code is imported from `url-insert-file-contents'.
251     (save-excursion
252       (let ((old-asynch (default-value 'url-be-asynchronous))
253             (old-caching (default-value 'url-automatic-caching))
254             (old-mode (default-value 'url-standalone-mode)))
255         (unwind-protect
256             (progn
257               (setq-default url-be-asynchronous nil)
258               (when no-cache
259                 (setq-default url-automatic-caching nil)
260                 (setq-default url-standalone-mode nil))
261               (let ((buf (current-buffer))
262                     (url-working-buffer (cdr (url-retrieve url no-cache))))
263                 (set-buffer url-working-buffer)
264                 (url-uncompress)
265                 (set-buffer buf)
266                 (insert-buffer url-working-buffer)
267                 (save-excursion
268                   (set-buffer url-working-buffer)
269                   (set-buffer-modified-p nil))
270                 (kill-buffer url-working-buffer)))
271           (setq-default url-be-asynchronous old-asynch)
272           (setq-default url-automatic-caching old-caching)
273           (setq-default url-standalone-mode old-mode))))
274     ;; Modify buffer coding system.
275     (decode-coding-region (point-min) (point-max) nnshimbun-coding-system)
276     (set-buffer-multibyte t)))
277
278 (deffoo nnshimbun-request-article (article &optional group server to-buffer)
279   (when (nnshimbun-possibly-change-group group server)
280     (if (stringp article)
281         (setq article (nnshimbun-search-id group article)))
282     (if (integerp article)
283         (nnshimbun-request-article-1 article group server to-buffer)
284       (nnheader-report 'nnml "Couldn't retrieve article: %s" (prin1-to-string article))
285       nil)))
286
287 (defun nnshimbun-request-article-1 (article &optional group server to-buffer)
288   (if (nnshimbun-backlog
289         (gnus-backlog-request-article
290          group article (or to-buffer nntp-server-buffer)))
291       (cons group article)
292     (let (header contents)
293       (when (setq header (save-excursion
294                            (set-buffer (nnshimbun-open-nov group))
295                            (and (nnheader-find-nov-line article)
296                                 (nnheader-parse-nov))))
297         (let* ((xref (substring (mail-header-xref header) 6))
298                (x-faces (cdr (or (assoc server nnshimbun-x-face-alist)
299                                  (assoc "default" nnshimbun-x-face-alist))))
300                (x-face (cdr (or (assoc group x-faces)
301                                 (assoc "default" x-faces)))))
302           (save-excursion
303             (set-buffer nnshimbun-buffer)
304             (erase-buffer)
305             (nnshimbun-retrieve-url xref)
306             (nnheader-message 6 "nnshimbun: Make contents...")
307             (goto-char (point-min))
308             (setq contents (funcall nnshimbun-make-contents header x-face))
309             (nnheader-message 6 "nnshimbun: Make contents...done"))))
310       (when contents
311         (save-excursion
312           (set-buffer (or to-buffer nntp-server-buffer))
313           (erase-buffer)
314           (insert contents)
315           (nnshimbun-backlog
316             (gnus-backlog-enter-article group article (current-buffer)))
317           (nnheader-report 'nnshimbun "Article %s retrieved" (mail-header-id header))
318           (cons group (mail-header-number header)))))))
319
320 (deffoo nnshimbun-request-group (group &optional server dont-check)
321   (let ((pathname-coding-system 'binary))
322     (cond
323      ((not (nnshimbun-possibly-change-group group server))
324       (nnheader-report 'nnshimbun "Invalid group (no such directory)"))
325      ((not (file-exists-p nnshimbun-current-directory))
326       (nnheader-report 'nnshimbun "Directory %s does not exist"
327                        nnshimbun-current-directory))
328      ((not (file-directory-p nnshimbun-current-directory))
329       (nnheader-report 'nnshimbun "%s is not a directory" nnshimbun-current-directory))
330      (dont-check
331       (nnheader-report 'nnshimbun "Group %s selected" group)
332       t)
333      (t
334       (let (beg end lines)
335         (save-excursion
336           (set-buffer (nnshimbun-open-nov group))
337           (goto-char (point-min))
338           (setq beg (ignore-errors (read (current-buffer))))
339           (goto-char (point-max))
340           (forward-line -1)
341           (setq end (ignore-errors (read (current-buffer)))
342                 lines (count-lines (point-min) (point-max))))
343         (nnheader-report 'nnshimbunw "Selected group %s" group)
344         (nnheader-insert "211 %d %d %d %s\n"
345                          lines (or beg 0) (or end 0) group))))))
346
347 (deffoo nnshimbun-request-scan (&optional group server)
348   (nnshimbun-possibly-change-group group server)
349   (nnshimbun-generate-nov-database group))
350
351 (deffoo nnshimbun-close-group (group &optional server)
352   (nnshimbun-write-nov group)
353   t)
354
355 (deffoo nnshimbun-request-list (&optional server)
356   (save-excursion
357     (set-buffer nntp-server-buffer)
358     (erase-buffer)
359     (dolist (group nnshimbun-groups)
360       (when (nnshimbun-possibly-change-group group server)
361         (let (beg end)
362           (save-excursion
363             (set-buffer (nnshimbun-open-nov group))
364             (goto-char (point-min))
365             (setq beg (ignore-errors (read (current-buffer))))
366             (goto-char (point-max))
367             (forward-line -1)
368             (setq end (ignore-errors (read (current-buffer)))))
369           (insert (format "%s %d %d n\n" group (or end 0) (or beg 0)))))))
370   t) ; return value
371
372 (eval-and-compile
373   (if (fboundp 'mime-entity-fetch-field)
374       ;; For Semi-Gnus.
375       (defun nnshimbun-insert-header (header)
376         (insert "Subject: " (or (mime-entity-fetch-field header 'Subject) "(none)") "\n"
377                 "From: " (or (mime-entity-fetch-field header 'From) "(nobody)") "\n"
378                 "Date: " (or (mail-header-date header) "") "\n"
379                 "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n"
380                 "References: " (or (mail-header-references header) "") "\n"
381                 "Lines: ")
382         (princ (or (mail-header-lines header) 0) (current-buffer))
383         (insert "\n")
384         (if (mail-header-xref header)
385             (insert (mail-header-xref header) "\n")))
386     ;; For pure Gnus.
387     (defun nnshimbun-insert-header (header)
388       (nnheader-insert-header header)
389       (delete-char -1)
390       (if (mail-header-xref header)
391           (insert (mail-header-xref header) "\n")))))
392
393 (deffoo nnshimbun-retrieve-headers (articles &optional group server fetch-old)
394   (when (nnshimbun-possibly-change-group group server)
395     (if (nnshimbun-retrieve-headers-with-nov articles fetch-old)
396         'nov
397       (save-excursion
398         (set-buffer nntp-server-buffer)
399         (erase-buffer)
400         (let (header)
401           (dolist (art articles)
402             (if (stringp art)
403                 (setq art (nnshimbun-search-id group art)))
404             (if (integerp art)
405                 (when (setq header
406                             (save-excursion
407                               (set-buffer (nnshimbun-open-nov group))
408                               (and (nnheader-find-nov-line art)
409                                    (nnheader-parse-nov))))
410                   (insert (format "220 %d Article retrieved.\n" art))
411                   (nnshimbun-insert-header header)
412                   (insert ".\n")
413                   (delete-region (point) (point-max))))))
414         'header))))
415
416 (defun nnshimbun-retrieve-headers-with-nov (articles &optional fetch-old)
417   (if (or gnus-nov-is-evil nnshimbun-nov-is-evil)
418       nil
419     (let ((nov (expand-file-name nnshimbun-nov-file-name nnshimbun-current-directory)))
420       (when (file-exists-p nov)
421         (save-excursion
422           (set-buffer nntp-server-buffer)
423           (erase-buffer)
424           (nnheader-insert-file-contents nov)
425           (if (and fetch-old (not (numberp fetch-old)))
426               t                         ; Don't remove anything.
427             (nnheader-nov-delete-outside-range
428              (if fetch-old (max 1 (- (car articles) fetch-old))
429                (car articles))
430              (car (last articles)))
431             t))))))
432
433
434
435 ;;; Nov Database Operations
436
437 (defun nnshimbun-generate-nov-database (group)
438   (prog1 (funcall nnshimbun-generate-nov group)
439     (nnshimbun-write-nov group)))
440
441 (defun nnshimbun-generate-nov-for-each-group (group)
442   (nnshimbun-possibly-change-group group)
443   (save-excursion
444     (set-buffer (nnshimbun-open-nov group))
445     (let (i)
446       (goto-char (point-max))
447       (forward-line -1)
448       (setq i (or (ignore-errors (read (current-buffer))) 0))
449       (dolist (header (save-excursion
450                         (set-buffer nnshimbun-buffer)
451                         (erase-buffer)
452                         (nnshimbun-retrieve-url (eval nnshimbun-index-url) t)
453                         (goto-char (point-min))
454                         (funcall nnshimbun-get-headers)))
455         (unless (nnshimbun-search-id group (mail-header-id header))
456           (mail-header-set-number header (setq i (1+ i)))
457           (goto-char (point-max))
458           (nnheader-insert-nov header)
459           (if nnshimbun-pre-fetch-article
460               (nnshimbun-request-article-1 i group nil nnshimbun-buffer)))))))
461
462 (defun nnshimbun-generate-nov-for-all-groups (&rest args)
463   (unless (and nnshimbun-nov-last-check
464                (< (nnshimbun-lapse-seconds nnshimbun-nov-last-check)
465                   nnshimbun-check-interval))
466     (save-excursion
467       (dolist (list (funcall nnshimbun-get-headers))
468         (let ((group (car list)))
469           (nnshimbun-possibly-change-group group)
470           (when (cdr list)
471             (set-buffer (nnshimbun-open-nov group))
472             (let (i)
473               (goto-char (point-max))
474               (forward-line -1)
475               (setq i (or (ignore-errors (read (current-buffer))) 0))
476               (dolist (header (cdr list))
477                 (unless (nnshimbun-search-id group (mail-header-id header))
478                   (mail-header-set-number header (setq i (1+ i)))
479                   (goto-char (point-max))
480                   (nnheader-insert-nov header)
481                   (if nnshimbun-pre-fetch-article
482                       (nnshimbun-request-article-1 i group nil nnshimbun-buffer))))))))
483       (nnshimbun-save-nov)
484       (setq nnshimbun-nov-last-check (current-time)))))
485
486 (defun nnshimbun-search-id (group id &optional nov)
487   (save-excursion
488     (set-buffer (nnshimbun-open-nov group))
489     (goto-char (point-min))
490     (let (found)
491       (while (and (not found)
492                   (search-forward id nil t)) ; We find the ID.
493         ;; And the id is in the fourth field.
494         (if (not (and (search-backward "\t" nil t 4)
495                       (not (search-backward "\t" (gnus-point-at-bol) t))))
496             (forward-line 1)
497           (forward-line 0)
498           (setq found t)))
499       (unless found
500         (goto-char (point-min))
501         (when (search-forward (concat "X-Nnshimbun-Original-Id: " id) nil t)
502           (forward-line 0)
503           (setq found t)))
504       (if found
505           (if nov
506               (nnheader-parse-nov)
507             ;; We return the article number.
508             (ignore-errors (read (current-buffer))))))))
509
510 (defun nnshimbun-nov-fix-header (group header args)
511   (save-excursion
512     (set-buffer (nnshimbun-open-nov group))
513     (when (nnheader-find-nov-line (mail-header-number header))
514       (dolist (arg args)
515         (if (eq (car arg) 'id)
516             (let ((extra (mail-header-extra header)) x)
517               (when (setq x (assq 'X-Nnshimbun-Original-Id extra))
518                 (setq extra (delq x extra)))
519               (mail-header-set-extra
520                header
521                (cons (cons 'X-Nnshimbun-Original-Id (cdr arg)) extra)))
522           (let ((func (intern (concat "mail-header-set-" (symbol-name (car arg))))))
523             (if (cdr arg) (eval (list func header (cdr arg)))))))
524       (let ((xref (mail-header-xref header)))
525         (when (string-match "^Xref: " xref)
526           (mail-header-set-xref header (substring xref 6))))
527       (delete-region (point) (progn (forward-line 1) (point)))
528       (nnheader-insert-nov header))))
529
530 (defun nnshimbun-open-nov (group)
531   (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
532     (if (buffer-live-p buffer)
533         buffer
534       (setq buffer (gnus-get-buffer-create
535                     (format " *nnshimbun overview %s %s*"
536                             (nnoo-current-server 'nnshimbun) group)))
537       (save-excursion
538         (set-buffer buffer)
539         (set (make-local-variable 'nnshimbun-nov-buffer-file-name)
540              (expand-file-name
541               nnshimbun-nov-file-name
542               (nnmail-group-pathname group nnshimbun-server-directory)))
543         (erase-buffer)
544         (when (file-exists-p nnshimbun-nov-buffer-file-name)
545           (nnheader-insert-file-contents nnshimbun-nov-buffer-file-name))
546         (set-buffer-modified-p nil))
547       (push (cons group buffer) nnshimbun-nov-buffer-alist)
548       buffer)))
549
550 (defun nnshimbun-write-nov (group)
551   (let ((buffer (cdr (assoc group nnshimbun-nov-buffer-alist))))
552     (when (buffer-live-p buffer)
553       (save-excursion
554         (set-buffer buffer)
555         (buffer-modified-p)
556         (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
557                              nil 'nomesg)))))
558
559 (defun nnshimbun-save-nov ()
560   (save-excursion
561     (while nnshimbun-nov-buffer-alist
562       (when (buffer-name (cdar nnshimbun-nov-buffer-alist))
563         (set-buffer (cdar nnshimbun-nov-buffer-alist))
564         (when (buffer-modified-p)
565           (nnmail-write-region 1 (point-max) nnshimbun-nov-buffer-file-name
566                                nil 'nomesg))
567         (set-buffer-modified-p nil)
568         (kill-buffer (current-buffer)))
569       (setq nnshimbun-nov-buffer-alist (cdr nnshimbun-nov-buffer-alist)))))
570
571
572
573 ;;; Server Initialize
574 (defun nnshimbun-possibly-change-group (group &optional server)
575   (when server
576     (unless (nnshimbun-server-opened server)
577       (nnshimbun-open-server server)))
578   (unless (gnus-buffer-live-p nnshimbun-buffer)
579     (setq nnshimbun-buffer
580           (save-excursion
581             (nnheader-set-temp-buffer
582              (format " *nnshimbun %s*" (nnoo-current-server 'nnshimbun))))))
583   (if (not group)
584       t
585     (let ((pathname (nnmail-group-pathname group nnshimbun-server-directory))
586           (pathname-coding-system 'binary))
587       (unless (equal pathname nnshimbun-current-directory)
588         (setq nnshimbun-current-directory pathname
589               nnshimbun-current-group group))
590       (unless (file-exists-p nnshimbun-current-directory)
591         (ignore-errors (make-directory nnshimbun-current-directory t)))
592       (cond
593        ((not (file-exists-p nnshimbun-current-directory))
594         (nnheader-report 'nnshimbun "Couldn't create directory: %s" nnshimbun-current-directory))
595        ((not (file-directory-p (file-truename nnshimbun-current-directory)))
596         (nnheader-report 'nnshimbun "Not a directory: %s" nnshimbun-current-directory))
597        (t t)))))
598
599
600
601 ;;; Misc Functions
602
603 (eval-and-compile
604   (if (fboundp 'eword-encode-string)
605       ;; For Semi-Gnus.
606       (defun nnshimbun-mime-encode-string (string)
607         (mapconcat
608          #'identity
609          (split-string (eword-encode-string (nnweb-decode-entities-string string)) "\n")
610          ""))
611     ;; For pure Gnus.
612     (defun nnshimbun-mime-encode-string (string)
613       (mapconcat
614        #'identity
615        (split-string
616         (with-temp-buffer
617           (insert (nnweb-decode-entities-string string))
618           (rfc2047-encode-region (point-min) (point-max))
619           (buffer-substring (point-min) (point-max)))
620         "\n")
621        ""))))
622
623 (defun nnshimbun-lapse-seconds (time)
624   (let ((now (current-time)))
625     (+ (* (- (car now) (car time)) 65536)
626        (- (nth 1 now) (nth 1 time)))))
627
628 (defun nnshimbun-make-date-string (year month day &optional time)
629   (format "%02d %s %04d %s +0900"
630           day
631           (aref [nil "Jan" "Feb" "Mar" "Apr" "May" "Jun"
632                      "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"]
633                 month)
634           (cond ((< year 69)
635                  (+ year 2000))
636                 ((< year 100)
637                  (+ year 1900))
638                 ((< year 1000)  ; possible 3-digit years.
639                  (+ year 1900))
640                 (t year))
641           (or time "00:00")))
642
643 (if (fboundp 'regexp-opt)
644     (defalias 'nnshimbun-regexp-opt 'regexp-opt)
645   (defun nnshimbun-regexp-opt (strings &optional paren)
646     "Return a regexp to match a string in STRINGS.
647 Each string should be unique in STRINGS and should not contain any regexps,
648 quoted or not.  If optional PAREN is non-nil, ensure that the returned regexp
649 is enclosed by at least one regexp grouping construct."
650     (let ((open-paren (if paren "\\(" "")) (close-paren (if paren "\\)" "")))
651       (concat open-paren (mapconcat 'regexp-quote strings "\\|") close-paren))))
652
653
654 ;; Fast fill-region function
655
656 (defvar nnshimbun-fill-column (min 80 (- (frame-width) 4)))
657
658 (defconst nnshimbun-kinsoku-bol-list
659   (funcall
660    (if (fboundp 'string-to-char-list)
661        'string-to-char-list
662      'string-to-list) "\
663 !)-_~}]:;',.?\e$B!"!#!$!%!&!'!(!)!*!+!,!-!.!/!0!1!2!3!4!5!6!7!8!9!:!;!<!=!>!?!@!A\e(B\
664 \e$B!B!C!D!E!G!I!K!M!O!Q!S!U!W!Y![!k!l!m!n$!$#$%$'$)$C$c$e$g$n%!%#%%%'%)%C%c%e%g%n%u%v\e(B"))
665
666 (defconst nnshimbun-kinsoku-eol-list
667   (funcall
668    (if (fboundp 'string-to-char-list)
669        'string-to-char-list
670      'string-to-list)
671    "({[`\e$B!F!H!J!L!N!P!R!T!V!X!Z!k!l!m!x\e(B"))
672
673 (defun nnshimbun-fill-line ()
674   (forward-line 0)
675   (let ((top (point)) chr)
676     (while (if (>= (move-to-column nnshimbun-fill-column)
677                    nnshimbun-fill-column)
678                (not (progn
679                       (if (memq (preceding-char) nnshimbun-kinsoku-eol-list)
680                           (progn
681                             (backward-char)
682                             (while (memq (preceding-char) nnshimbun-kinsoku-eol-list)
683                               (backward-char))
684                             (insert "\n"))
685                         (while (memq (setq chr (following-char)) nnshimbun-kinsoku-bol-list)
686                           (forward-char))
687                         (if (looking-at "\\s-+")
688                             (or (eolp) (delete-region (point) (match-end 0)))
689                           (or (> (char-width chr) 1)
690                               (re-search-backward "\\<" top t)
691                               (end-of-line)))
692                         (or (eolp) (insert "\n"))))))
693       (setq top (point))))
694   (forward-line 1)
695   (not (eobp)))
696
697 (defsubst nnshimbun-shallow-rendering ()
698   (goto-char (point-min))
699   (while (search-forward "<p>" nil t)
700     (insert "\n\n"))
701   (goto-char (point-min))
702   (while (search-forward "<br>" nil t)
703     (insert "\n"))
704   (nnweb-remove-markup)
705   (nnweb-decode-entities)
706   (goto-char (point-min))
707   (while (nnshimbun-fill-line))
708   (goto-char (point-min))
709   (when (skip-chars-forward "\n")
710     (delete-region (point-min) (point)))
711   (while (search-forward "\n\n" nil t)
712     (let ((p (point)))
713       (when (skip-chars-forward "\n")
714         (delete-region p (point)))))
715   (goto-char (point-max))
716   (when (skip-chars-backward "\n")
717     (delete-region (point) (point-max)))
718   (insert "\n"))
719
720 (defun nnshimbun-make-text-or-html-contents (header &optional x-face)
721   (let ((case-fold-search t) (html t) (start))
722     (when (and (search-forward nnshimbun-contents-start nil t)
723                (setq start (point))
724                (search-forward nnshimbun-contents-end nil t))
725       (delete-region (point-min) start)
726       (delete-region (- (point) (length nnshimbun-contents-end)) (point-max))
727       (nnshimbun-shallow-rendering)
728       (setq html nil))
729     (goto-char (point-min))
730     (nnshimbun-insert-header header)
731     (insert "Content-Type: " (if html "text/html" "text/plain")
732             "; charset=ISO-2022-JP\nMIME-Version: 1.0\n")
733     (when x-face
734       (insert x-face)
735       (unless (bolp)
736         (insert "\n")))
737     (insert "\n")
738     (encode-coding-string (buffer-string)
739                           (mime-charset-to-coding-system "ISO-2022-JP"))))
740
741 (defun nnshimbun-make-html-contents (header &optional x-face)
742   (let (start)
743     (when (and (search-forward nnshimbun-contents-start nil t)
744                (setq start (point))
745                (search-forward nnshimbun-contents-end nil t))
746       (delete-region (point-min) start)
747       (delete-region (- (point) (length nnshimbun-contents-end)) (point-max)))
748     (goto-char (point-min))
749     (nnshimbun-insert-header header)
750     (insert "Content-Type: text/html; charset=ISO-2022-JP\n"
751             "MIME-Version: 1.0\n")
752     (when x-face
753       (insert x-face)
754       (unless (bolp)
755         (insert "\n")))
756     (insert "\n")
757     (encode-coding-string (buffer-string)
758                           (mime-charset-to-coding-system "ISO-2022-JP"))))
759
760
761
762 ;;; www.asahi.com
763
764 (defun nnshimbun-asahi-get-headers ()
765   (when (search-forward "\n<!-- Start of past -->\n" nil t)
766     (delete-region (point-min) (point))
767     (when (search-forward "\n<!-- End of past -->\n" nil t)
768       (forward-line -1)
769       (delete-region (point) (point-max))
770       (goto-char (point-min))
771       (let (headers)
772         (while (re-search-forward
773                 "^\e$B"#\e(B<a href=\"\\(\\([0-9][0-9][0-9][0-9]\\)/past/\\([A-z]*[0-9]*\\)\\.html\\)\"> *"
774                 nil t)
775           (let ((id (format "<%s%s%%%s>"
776                             (match-string 2)
777                             (match-string 3)
778                             nnshimbun-current-group))
779                 (url (match-string 1)))
780             (push (make-full-mail-header
781                    0
782                    (nnshimbun-mime-encode-string
783                     (mapconcat 'identity
784                                (split-string
785                                 (buffer-substring
786                                  (match-end 0)
787                                  (progn (search-forward "<br>" nil t) (point)))
788                                 "<[^>]+>")
789                                ""))
790                    nnshimbun-from-address
791                    "" id "" 0 0 (concat nnshimbun-url url))
792                   headers)))
793         (setq headers (nreverse headers))
794         (let ((i 0))
795           (while (and (nth i headers)
796                       (re-search-forward
797                        "^\\[\\([0-9][0-9]\\)/\\([0-9][0-9]\\) \\([0-9][0-9]:[0-9][0-9]\\)\\]"
798                        nil t))
799             (let ((month (string-to-number (match-string 1)))
800                   (date (decode-time (current-time))))
801               (mail-header-set-date
802                (nth i headers)
803                (nnshimbun-make-date-string
804                 (if (and (eq 12 month) (eq 1 (nth 4 date)))
805                     (1- (nth 5 date))
806                   (nth 5 date))
807                 month
808                 (string-to-number (match-string 2))
809                 (match-string 3))))
810             (setq i (1+ i))))
811         (nreverse headers)))))
812
813
814
815 ;;; www.sponichi.co.jp
816
817 (defun nnshimbun-sponichi-get-headers ()
818   (when (search-forward "\e$B%K%e!<%9%$%s%G%C%/%9\e(B" nil t)
819     (delete-region (point-min) (point))
820     (when (search-forward "\e$B%"%I%?%0\e(B" nil t)
821       (forward-line 2)
822       (delete-region (point) (point-max))
823       (goto-char (point-min))
824       (let ((case-fold-search t) headers)
825         (while (re-search-forward
826                 "^<a href=\"/\\(\\([A-z]*\\)/kiji/\\([0-9][0-9][0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)\\.html\\)\">"
827                 nil t)
828           (let ((url (match-string 1))
829                 (id (format "<%s%s%s%s%%%s>"
830                             (match-string 3)
831                             (match-string 4)
832                             (match-string 5)
833                             (match-string 6)
834                             nnshimbun-current-group))
835                 (date (nnshimbun-make-date-string
836                        (string-to-number (match-string 3))
837                        (string-to-number (match-string 4))
838                        (string-to-number (match-string 5)))))
839             (push (make-full-mail-header
840                    0
841                    (nnshimbun-mime-encode-string
842                     (mapconcat 'identity
843                                (split-string
844                                 (buffer-substring
845                                  (match-end 0)
846                                  (progn (search-forward "<br>" nil t) (point)))
847                                 "<[^>]+>")
848                                ""))
849                    nnshimbun-from-address
850                    date id "" 0 0 (concat nnshimbun-url url))
851                   headers)))
852         headers))))
853
854
855
856 ;;; CNET Japan
857
858 (defun nnshimbun-cnet-get-headers ()
859   (let ((case-fold-search t) headers)
860     (while (search-forward "\n<!--*****\e$B8+=P$7\e(B*****-->\n" nil t)
861       (let ((subject (buffer-substring (point) (gnus-point-at-eol)))
862             (point (point)))
863         (forward-line -2)
864         (when (looking-at "<a href=\"/\\(News/\\([0-9][0-9][0-9][0-9]\\)/Item/\\([0-9][0-9]\\([0-9][0-9]\\)\\([0-9][0-9]\\)-[0-9]+\\).html\\)\">")
865           (let ((url (match-string 1))
866                 (id  (format "<%s%s%%%s>"
867                              (match-string 2)
868                              (match-string 3)
869                              nnshimbun-current-group))
870                 (date (nnshimbun-make-date-string
871                        (string-to-number (match-string 2))
872                        (string-to-number (match-string 4))
873                        (string-to-number (match-string 5)))))
874             (push (make-full-mail-header
875                    0
876                    (nnshimbun-mime-encode-string subject)
877                    nnshimbun-from-address
878                    date id "" 0 0 (concat nnshimbun-url url))
879                   headers)))
880         (goto-char point)))
881     headers))
882
883
884
885 ;;; Wired
886
887 (defun nnshimbun-wired-get-all-headers ()
888   (save-excursion
889     (set-buffer nnshimbun-buffer)
890     (let ((group-header-alist (mapcar (lambda (g) (cons g nil)) nnshimbun-groups))
891           (case-fold-search t)
892           (regexp (format
893                    "<a href=\"\\(%s\\|/\\)\\(news/news/\\(%s\\)/story/\\(\\([0-9][0-9][0-9][0-9]\\)\\([0-9][0-9]\\)\\([0-9][0-9]\\)[0-9]+\\)\\.html\\)\"><b>"
894                    (regexp-quote nnshimbun-url)
895                    (nnshimbun-regexp-opt nnshimbun-groups))))
896       (dolist (xover (list (concat nnshimbun-url "news/news/index.html")
897                            (concat nnshimbun-url "news/news/last_seven.html")))
898         (erase-buffer)
899         (nnshimbun-retrieve-url xover t)
900         (goto-char (point-min))
901         (while (re-search-forward regexp nil t)
902           (let* ((url   (concat nnshimbun-url (match-string 2)))
903                  (group (downcase (match-string 3)))
904                  (id    (format "<%s%%%s>" (match-string 4) group))
905                  (date  (nnshimbun-make-date-string
906                          (string-to-number (match-string 5))
907                          (string-to-number (match-string 6))
908                          (string-to-number (match-string 7))))
909                  (header (make-full-mail-header
910                           0
911                           (nnshimbun-mime-encode-string
912                            (mapconcat 'identity
913                                       (split-string
914                                        (buffer-substring
915                                         (match-end 0)
916                                         (progn (search-forward "</b>" nil t) (point)))
917                                        "<[^>]+>")
918                                       ""))
919                           nnshimbun-from-address
920                           date id "" 0 0 url))
921                  (x (assoc group group-header-alist)))
922             (setcdr x (cons header (cdr x))))))
923       group-header-alist)))
924
925
926
927 ;;; www.yomiuri.co.jp
928
929 (defun nnshimbun-yomiuri-get-all-headers ()
930   (save-excursion
931     (set-buffer nnshimbun-buffer)
932     (erase-buffer)
933     (nnshimbun-retrieve-url (eval nnshimbun-index-url) t)
934     (let ((case-fold-search t)
935           (group-header-alist (mapcar (lambda (g) (cons g nil)) nnshimbun-groups)))
936       (dolist (group nnshimbun-groups)
937         (let (start)
938           (goto-char (point-min))
939           (when (and (search-forward (format "\n<!-- /news/%s=start -->\n" group) nil t)
940                      (setq start (point))
941                      (search-forward (format "\n<!-- /news/%s=end -->\n" group) nil t))
942             (forward-line -1)
943             (save-restriction
944               (narrow-to-region start (point))
945               (goto-char start)
946               (while (re-search-forward
947                       "<a href=\"/\\([0-9]+\\)/\\(\\(\\([0-9][0-9][0-9][0-9]\\)\\([0-9][0-9]\\)\\([0-9][0-9]\\)[A-z0-9]+\\)\\.htm\\)\"[^>]*>"
948                       nil t)
949                 (let ((url   (concat (match-string 1) "a/" (match-string 2)))
950                       (id    (format "<%s%s%%%s>"
951                                      (match-string 1)
952                                      (match-string 3)
953                                      group))
954                       (year  (string-to-number (match-string 4)))
955                       (month (string-to-number (match-string 5)))
956                       (day   (string-to-number (match-string 6)))
957                       (subject (mapconcat
958                                 'identity
959                                 (split-string
960                                  (buffer-substring
961                                   (match-end 0)
962                                   (progn (search-forward "<br>" nil t) (point)))
963                                  "<[^>]+>")
964                                 ""))
965                       date x)
966                   (when (string-match "^\e$B"!\e(B" subject)
967                     (setq subject (substring subject (match-end 0))))
968                   (if (string-match "(\\([0-9][0-9]:[0-9][0-9]\\))$" subject)
969                       (setq date (nnshimbun-make-date-string
970                                   year month day (match-string 1 subject))
971                             subject (substring subject 0 (match-beginning 0)))
972                     (setq date (nnshimbun-make-date-string year month day)))
973                   (setcdr (setq x (assoc group group-header-alist))
974                           (cons (make-full-mail-header
975                                  0
976                                  (nnshimbun-mime-encode-string subject)
977                                  nnshimbun-from-address
978                                  date id "" 0 0 (concat nnshimbun-url url))
979                                 (cdr x)))))))))
980       group-header-alist)))
981
982
983
984 ;;; Zdnet Japan
985
986 (defun nnshimbun-zdnet-get-headers ()
987   (let ((case-fold-search t) headers)
988     (goto-char (point-min))
989     (let (start)
990       (while (and (search-forward "<!--" nil t)
991                   (setq start (- (point) 4))
992                   (search-forward "-->" nil t))
993         (delete-region start (point))))
994     (goto-char (point-min))
995     (while (re-search-forward
996             "<a href=\"\\(\\([0-9][0-9]\\)\\([0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([^\\.]+\\).html\\)\"><font size=\"4\"><strong>"
997             nil t)
998       (let ((year  (+ 2000 (string-to-number (match-string 2))))
999             (month (string-to-number (match-string 3)))
1000             (day   (string-to-number (match-string 4)))
1001             (id    (format "<%s%s%s%s%%%s>"
1002                            (match-string 2)
1003                            (match-string 3)
1004                            (match-string 4)
1005                            (match-string 5)
1006                            nnshimbun-current-group))
1007             (url (match-string 1)))
1008         (push (make-full-mail-header
1009                0
1010                (nnshimbun-mime-encode-string
1011                 (mapconcat 'identity
1012                            (split-string
1013                             (buffer-substring
1014                              (match-end 0)
1015                              (progn (search-forward "</a>" nil t) (point)))
1016                             "<[^>]+>")
1017                            ""))
1018                nnshimbun-from-address
1019                (nnshimbun-make-date-string year month day)
1020                id  "" 0 0 (concat nnshimbun-url url))
1021               headers)))
1022     (nreverse headers)))
1023
1024
1025
1026 (provide 'nnshimbun)
1027 ;;; nnshimbun.el ends here.