Importing Oort Gnus v0.01.
[elisp/gnus.git-] / lisp / nnrss.el
1 ;;; nnrss.el --- interfacing with RSS
2 ;; Copyright (C) 2001  Free Software Foundation, Inc.
3
4 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
5 ;; Keywords: RSS
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published
11 ;; by the Free Software Foundation; either version 2, or (at your
12 ;; option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 ;; General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29
30 (require 'nnoo)
31 (require 'nnmail)
32 (require 'message)
33 (require 'mm-util)
34 (require 'gnus-util)
35 (require 'time-date)
36 (eval-when-compile
37   (ignore-errors
38     (require 'xml)
39     (require 'w3)
40     (require 'w3-forms)
41     (require 'nnweb)))
42 ;; Report failure to find w3 at load time if appropriate.
43 (eval '(progn
44          (require 'xml)
45          (require 'w3)
46          (require 'w3-forms)
47          (require 'nnweb)))
48
49 (nnoo-declare nnrss)
50
51 (defvoo nnrss-directory (nnheader-concat gnus-directory "rss/")
52   "Where nnrss will save its files.")
53
54 ;; (group max rss-url)
55 (defvoo nnrss-server-data nil)
56
57 ;; (num timestamp url subject author date extra)
58 (defvoo nnrss-group-data nil)
59 (defvoo nnrss-group-max 0)
60 (defvoo nnrss-group-min 1)
61 (defvoo nnrss-group nil)
62 (defvoo nnrss-group-hashtb nil)
63 (defvoo nnrss-status-string "")
64
65 (defconst nnrss-version "nnrss 1.0")
66
67 (defvar nnrss-group-alist
68   '(("MacWeek"
69      "http://macweek.zdnet.com/macweek.xml")
70     ("Linux.Weekly.News"
71      "http://lwn.net/headlines/rss")
72     ("Motley.Fool"
73      "http://www.fool.com/About/headlines/rss_headlines.asp")
74     ("NewsForge.rdf"
75      "http://www.newsforge.com/newsforge.rdf")
76     ("Slashdot"
77      "http://www.slashdot.com/slashdot.rdf")
78     ("CNN"
79      "http://www.cnn.com/cnn.rss")
80     ("FreshMeat"
81      "http://freshmeat.net/backend/fm.rdf")
82     ("The.Guardian.newspaper"
83      "http://www.guardianunlimited.co.uk/rss/1,,,00.xml")
84     ("MonkeyFist.rdf"
85      "http://monkeyfist.com/rdf.php3")
86     ("NewsForge"
87      "http://www.newsforge.com/newsforge.rss")
88     ("Reuters.Health"
89      "http://www.reutershealth.com/eline.rss")
90     ("Salon"
91      "http://www.salon.com/feed/RDF/salon_use.rdf")
92     ("Wired"
93      "http://www.wired.com/news_drop/netcenter/netcenter.rdf")
94     ("ITN"
95      "http://www.itn.co.uk/itn.rdf")
96     ("Meerkat"
97      "http://www.oreillynet.com/meerkat/?_fl=rss10")
98     ("MonkeyFist"
99      "http://monkeyfist.com/rss1.php3")
100     ("Reuters.Health.rdf"
101      "http://www.reutershealth.com/eline.rdf")))
102
103 (defvar nnrss-use-local nil)
104
105 (nnoo-define-basics nnrss)
106
107 ;;; Interface functions
108
109 (deffoo nnrss-retrieve-headers (articles &optional group server fetch-old)
110   (nnrss-possibly-change-group group server)
111   (let (e)
112     (save-excursion
113       (set-buffer nntp-server-buffer)
114       (erase-buffer)
115       (dolist (article articles)
116         (if (setq e (assq article nnrss-group-data))
117             (insert (number-to-string (car e)) "\t" ;; number
118                     (if (nth 3 e)
119                         (nnrss-string-as-multibyte (nth 3 e)) "")
120                     "\t" ;; subject
121                     (if (nth 4 e)
122                         (nnrss-string-as-multibyte (nth 4 e)) "")
123                     "\t" ;;from
124                     (or (nth 5 e) "")
125                     "\t" ;; date
126                     (format "<%d@%s.nnrss>" (car e) group)
127                     "\t" ;; id
128                     "\t" ;; refs
129                     "0" "\t" ;; chars
130                     "0" "\t" ;; lines
131                     "\n")))))
132   'nov)
133
134 (deffoo nnrss-request-group (group &optional server dont-check)
135   (nnrss-possibly-change-group group server)
136   (if dont-check
137       t
138     (nnrss-check-group group server)
139     (nnheader-report 'nnrss "Opened group %s" group)
140     (nnheader-insert
141      "211 %d %d %d %s\n" nnrss-group-max nnrss-group-min nnrss-group-max
142      (prin1-to-string group)
143      t)))
144
145 (deffoo nnrss-close-group (group &optional server)
146   t)
147
148 (deffoo nnrss-request-article (article &optional group server buffer)
149   (nnrss-possibly-change-group group server)
150   (let ((e (assq article nnrss-group-data))
151         (nntp-server-buffer (or buffer nntp-server-buffer))
152         post err)
153     (when e
154       (catch 'error
155         (with-current-buffer nntp-server-buffer
156           (erase-buffer)
157           (goto-char (point-min))
158           (if (nth 3 e)
159               (insert "Subject: " (nnrss-string-as-multibyte (nth 3 e)) "\n"))
160           (if (nth 4 e)
161               (insert "From: " (nnrss-string-as-multibyte (nth 4 e)) "\n"))
162           (if (nth 5 e)
163               (insert "Date: " (nnrss-string-as-multibyte (nth 5 e)) "\n"))
164           (insert "Message-ID: " (format "<%d@%s.nnrss>" (car e) group) "\n")
165           (insert "\n")
166           (if (nth 6 e)
167               (let ((point (point)))
168                 (insert (nnrss-string-as-multibyte (nth 6 e)) "\n\n")
169                 (fill-region point (point))))
170           (if (nth 2 e)
171               (insert (nth 2 e) "\n")))))
172     (cond
173      (err
174       (nnheader-report 'nnrss err))
175      ((not e)
176       (nnheader-report 'nnrss "No such id: %d" article))
177      (t
178       (nnheader-report 'nnrss "Article %s retrieved" (car e))
179       ;; We return the article number.
180       (cons nnrss-group (car e))))))
181
182 (deffoo nnrss-request-list (&optional server)
183   (nnrss-possibly-change-group nil server)
184   (nnrss-generate-active)
185   t)
186
187 (deffoo nnrss-open-server (server &optional defs connectionless)
188   (nnoo-change-server 'nnrss server defs)
189   t)
190
191 (deffoo nnrss-request-expire-articles
192     (articles group &optional server force)
193   (nnrss-possibly-change-group group server)
194   (let (e changed days)
195     (dolist (art articles)
196       (when (setq e (assq art nnrss-group-data))
197       (if (nnmail-expired-article-p
198            group
199            (if (listp (setq days (nth 1 e))) days (days-to-time days))
200            force)
201           (setq nnrss-group-data (delq e nnrss-group-data)
202                 changed t))))
203     (if changed
204         (nnrss-save-group-data group server))))
205
206 (deffoo nnrss-request-delete-group (group &optional force server)
207   (nnrss-possibly-change-group group server)
208   (setq nnrss-server-data
209         (delq (assoc group nnrss-server-data) nnrss-server-data))
210   (nnrss-save-server-data server)
211   (let ((file (expand-file-name (concat group (and server
212                                                    (not (equal server ""))
213                                                    "-")
214                                         server ".el") nnrss-directory)))
215     (delete-file file))
216   t)
217
218 (nnoo-define-skeleton nnrss)
219
220 ;;; Internal functions
221
222 (defun nnrss-possibly-change-group (&optional group server)
223   (when (and server
224              (not (nnrss-server-opened server)))
225     (nnrss-read-server-data server)
226     (nnrss-open-server server))
227   (when (and group (not (equal group nnrss-group)))
228     (nnrss-read-group-data group server)
229     (setq nnrss-group group)))
230
231 (defun nnrss-generate-active ()
232   (save-excursion
233     (set-buffer nntp-server-buffer)
234     (erase-buffer)
235     (dolist (elem nnrss-group-alist)
236       (insert (prin1-to-string (car elem)) " 0 1 y\n"))
237     (dolist (elem nnrss-server-data)
238       (unless (assoc (car elem) nnrss-group-alist)
239         (insert (prin1-to-string (car elem)) " 0 1 y\n")))))
240
241 ;;; Data functions
242
243 (defun nnrss-read-server-data (server)
244   (setq nnrss-server-data nil)
245   (let ((file (expand-file-name (concat "nnrss" (and server
246                                                      (not (equal server ""))
247                                                      "-")
248                                         server
249                                         ".el")
250                                 nnrss-directory)))
251     (when (file-exists-p file)
252       (with-temp-buffer
253         (let ((coding-system-for-read 'binary))
254           (insert-file-contents file))
255         (goto-char (point-min))
256         (eval-buffer)))))
257
258 (defun nnrss-save-server-data (server)
259   (gnus-make-directory nnrss-directory)
260   (let ((file (expand-file-name (concat "nnrss" (and server
261                                                      (not (equal server ""))
262                                                      "-")
263                                         server ".el")
264                                 nnrss-directory)))
265     (let ((coding-system-for-write 'binary))
266       (with-temp-file file
267         (insert "(setq nnrss-server-data '"
268                 (prin1-to-string nnrss-server-data)
269                 ")\n")))))
270
271 (defun nnrss-read-group-data (group server)
272   (setq nnrss-group-data nil)
273   (setq nnrss-group-hashtb (gnus-make-hashtable))
274   (let ((pair (assoc group nnrss-server-data)))
275     (setq nnrss-group-max (or (cadr pair) 0))
276     (setq nnrss-group-min (+ nnrss-group-max 1)))
277   (let ((file (expand-file-name (concat group (and server
278                                                    (not (equal server ""))
279                                                    "-")
280                                         server ".el")
281                                 nnrss-directory)))
282     (when (file-exists-p file)
283       (with-temp-buffer
284         (let ((coding-system-for-read 'binary))
285           (insert-file-contents file))
286         (goto-char (point-min))
287         (eval-buffer))
288       (dolist (e nnrss-group-data)
289         (gnus-sethash (nth 2 e) e nnrss-group-hashtb)
290         (if (and (car e) (> nnrss-group-min (car e)))
291             (setq nnrss-group-min (car e)))
292         (if (and (car e) (< nnrss-group-max (car e)))
293             (setq nnrss-group-max (car e)))))))
294
295 (defun nnrss-save-group-data (group server)
296   (gnus-make-directory nnrss-directory)
297   (let ((file (expand-file-name (concat group (and server
298                                                    (not (equal server ""))
299                                                    "-")
300                                         server ".el")
301                                 nnrss-directory)))
302     (let ((coding-system-for-write 'binary))
303       (with-temp-file file
304         (insert "(setq nnrss-group-data '"
305                 (prin1-to-string nnrss-group-data)
306                 ")\n")))))
307
308 ;;; URL interface
309
310 (defun nnrss-no-cache (url)
311   "")
312
313 (defun nnrss-insert-w3 (url)
314   (require 'url)
315   (require 'url-cache)
316   (let ((url-cache-creation-function 'nnrss-no-cache))
317     (mm-with-unibyte-current-buffer
318       (nnweb-insert url))))
319
320 (defun nnrss-decode-entities-unibyte-string (string)
321   (mm-with-unibyte-buffer
322     (insert string)
323     (nnweb-decode-entities)
324     (buffer-substring (point-min) (point-max))))
325
326 (defalias 'nnrss-insert 'nnrss-insert-w3)
327
328 (if (featurep 'xemacs)
329     (defalias 'nnrss-string-as-multibyte 'identity)
330   (defalias 'nnrss-string-as-multibyte 'string-as-multibyte))
331
332 ;;; Snarf functions
333
334 (defun nnrss-check-group (group server)
335   (let ((w3-html-entities (cons '(nbsp . 32) w3-html-entities))
336         file xml subject url extra changed author date)
337     (mm-with-unibyte-buffer
338       (if (and nnrss-use-local
339                (file-exists-p (setq file (expand-file-name
340                                           (concat group ".xml")
341                                           nnrss-directory))))
342           (insert-file-contents file)
343         (setq url (or (nth 2 (assoc group nnrss-server-data))
344                       (second (assoc group nnrss-group-alist))))
345         (unless url
346           (setq url
347                 (read-string (format "RSS url of %s: " group "http://")))
348           (let ((pair (assoc group nnrss-server-data)))
349             (if pair
350                 (setcdr (cdr pair) (list url))
351               (push (list group nnrss-group-max url) nnrss-server-data)))
352           (setq changed t))
353         (nnrss-insert url))
354       (goto-char (point-min))
355       (while (re-search-forward "\r\n?" nil t)
356         (replace-match "\n"))
357       (goto-char (point-min))
358       (if (re-search-forward "<rdf\\|<rss" nil t)
359           (goto-char (match-beginning 0)))
360       (setq xml (xml-parse-region (point) (point-max))))
361     (while (and xml (not (assq 'item xml)))
362       (unless (listp (car (setq xml (cddar xml))))
363         (setq xml nil)))
364     (dolist (item xml)
365        (when (and (listp item)
366                   (eq 'item (car item))
367                   (setq url (caddr (assq 'link (cddr item))))
368                   (setq url (nnrss-decode-entities-unibyte-string url))
369                   (not (gnus-gethash url nnrss-group-hashtb)))
370          (setq subject (caddr (assq 'title (cddr item))))
371          (setq extra (or (caddr (assq 'description (cddr item)))
372                          (caddr (assq 'dc:description (cddr item)))))
373          (setq author (caddr (assq 'dc:creator (cddr item))))
374          (setq date (or (caddr (assq 'dc:date (cddr item)))
375                         (message-make-date)))
376          (push
377           (list
378            (incf nnrss-group-max)
379            (time-to-days (current-time))
380            url
381            (and subject (nnrss-decode-entities-unibyte-string subject))
382            (and author (nnrss-decode-entities-unibyte-string author))
383            date
384            (and extra (nnrss-decode-entities-unibyte-string extra)))
385           nnrss-group-data)
386          (gnus-sethash url (car nnrss-group-data) nnrss-group-hashtb)
387          (setq changed t)))
388     (when changed
389         (nnrss-save-group-data group server)
390         (let ((pair (assoc group nnrss-server-data)))
391           (if pair
392               (setcar (cdr pair) nnrss-group-max)
393             (push (list group nnrss-group-max) nnrss-server-data)))
394         (nnrss-save-server-data server))))
395
396 (provide 'nnrss)
397
398 ;;; nnrss.el ends here