Commit | Line | Data |
---|---|---|
23f87bed | 1 | ;;; nnrss.el --- interfacing with RSS |
e84b4b86 | 2 | |
8abf1b22 | 3 | ;; Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, |
5df4f04c | 4 | ;; 2008, 2009, 2010, 2011 Free Software Foundation, Inc. |
23f87bed MB |
5 | |
6 | ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu> | |
7 | ;; Keywords: RSS | |
8 | ||
9 | ;; This file is part of GNU Emacs. | |
10 | ||
5e809f55 GM |
11 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
12 | ;; it under the terms of the GNU General Public License as published by | |
13 | ;; the Free Software Foundation, either version 3 of the License, or | |
14 | ;; (at your option) any later version. | |
23f87bed | 15 | |
5e809f55 GM |
16 | ;; GNU Emacs is distributed in the hope that it will be useful, |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
23f87bed MB |
20 | |
21 | ;; You should have received a copy of the GNU General Public License | |
5e809f55 | 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
23f87bed MB |
23 | |
24 | ;;; Commentary: | |
25 | ||
26 | ;;; Code: | |
27 | ||
f0b7f5a8 | 28 | ;; For Emacs <22.2 and XEmacs. |
0f7cbeb9 GM |
29 | (eval-and-compile |
30 | (unless (fboundp 'declare-function) (defmacro declare-function (&rest r)))) | |
31 | ||
23f87bed MB |
32 | (eval-when-compile (require 'cl)) |
33 | ||
34 | (require 'gnus) | |
35 | (require 'nnoo) | |
36 | (require 'nnmail) | |
37 | (require 'message) | |
38 | (require 'mm-util) | |
39 | (require 'gnus-util) | |
40 | (require 'time-date) | |
41 | (require 'rfc2231) | |
42 | (require 'mm-url) | |
91472578 MB |
43 | (require 'rfc2047) |
44 | (require 'mml) | |
23f87bed MB |
45 | (eval-when-compile |
46 | (ignore-errors | |
91472578 | 47 | (require 'xml))) |
23f87bed MB |
48 | (eval '(require 'xml)) |
49 | ||
50 | (nnoo-declare nnrss) | |
51 | ||
52 | (defvoo nnrss-directory (nnheader-concat gnus-directory "rss/") | |
53 | "Where nnrss will save its files.") | |
54 | ||
01c52d31 MB |
55 | (defvoo nnrss-ignore-article-fields '(slash:comments) |
56 | "*List of fields that should be ignored when comparing RSS articles. | |
57 | Some RSS feeds update article fields during their lives, e.g. to | |
58 | indicate the number of comments or the number of times the | |
59 | articles have been seen. However, if there is a difference | |
60 | between the local article and the distant one, the latter is | |
61 | considered to be new. To avoid this and discard some fields, set | |
62 | this variable to the list of fields to be ignored.") | |
63 | ||
23f87bed MB |
64 | ;; (group max rss-url) |
65 | (defvoo nnrss-server-data nil) | |
66 | ||
67 | ;; (num timestamp url subject author date extra) | |
68 | (defvoo nnrss-group-data nil) | |
69 | (defvoo nnrss-group-max 0) | |
70 | (defvoo nnrss-group-min 1) | |
71 | (defvoo nnrss-group nil) | |
01c52d31 | 72 | (defvoo nnrss-group-hashtb (make-hash-table :test 'equal)) |
23f87bed MB |
73 | (defvoo nnrss-status-string "") |
74 | ||
75 | (defconst nnrss-version "nnrss 1.0") | |
76 | ||
77 | (defvar nnrss-group-alist '() | |
78 | "List of RSS addresses.") | |
79 | ||
6b958814 G |
80 | (defvar nnrss-use-local nil |
81 | "If non-nil nnrss will read the feeds from local files in nnrss-directory.") | |
23f87bed MB |
82 | |
83 | (defvar nnrss-description-field 'X-Gnus-Description | |
84 | "Field name used for DESCRIPTION. | |
85 | To use the description in headers, put this name into `nnmail-extra-headers'.") | |
86 | ||
87 | (defvar nnrss-url-field 'X-Gnus-Url | |
88 | "Field name used for URL. | |
89 | To use the description in headers, put this name into `nnmail-extra-headers'.") | |
90 | ||
91 | (defvar nnrss-content-function nil | |
92 | "A function which is called in `nnrss-request-article'. | |
93 | The arguments are (ENTRY GROUP ARTICLE). | |
91472578 | 94 | ENTRY is the record of the current headline. GROUP is the group name. |
23f87bed MB |
95 | ARTICLE is the article number of the current headline.") |
96 | ||
91472578 | 97 | (defvar nnrss-file-coding-system mm-universal-coding-system |
01c52d31 MB |
98 | "*Coding system used when reading and writing files. |
99 | If you run Gnus with various versions of Emacsen, the value of this | |
100 | variable should be the coding system that all those Emacsen support. | |
101 | Note that you have to regenerate all the nnrss groups if you change | |
102 | the value. Moreover, you should be patient even if you are made to | |
103 | read the same articles twice, that arises for the difference of the | |
104 | versions of xml.el.") | |
91472578 | 105 | |
82fe1aed MB |
106 | (defvar nnrss-compatible-encoding-alist |
107 | (delq nil (mapcar (lambda (elem) | |
108 | (if (and (mm-coding-system-p (car elem)) | |
109 | (mm-coding-system-p (cdr elem))) | |
110 | elem)) | |
111 | mm-charset-override-alist)) | |
91472578 MB |
112 | "Alist of encodings and those supersets. |
113 | The cdr of each element is used to decode data if it is available when | |
7dafe00b | 114 | the car is what the data specify as the encoding. Or, the car is used |
91472578 MB |
115 | for decoding when the cdr that the data specify is not available.") |
116 | ||
23f87bed MB |
117 | (nnoo-define-basics nnrss) |
118 | ||
119 | ;;; Interface functions | |
120 | ||
91472578 MB |
121 | (defsubst nnrss-format-string (string) |
122 | (gnus-replace-in-string string " *\n *" " ")) | |
123 | ||
124 | (defun nnrss-decode-group-name (group) | |
125 | (if (and group (mm-coding-system-p 'utf-8)) | |
126 | (setq group (mm-decode-coding-string group 'utf-8)) | |
127 | group)) | |
ad136a7c | 128 | |
23f87bed | 129 | (deffoo nnrss-retrieve-headers (articles &optional group server fetch-old) |
91472578 | 130 | (setq group (nnrss-decode-group-name group)) |
23f87bed MB |
131 | (nnrss-possibly-change-group group server) |
132 | (let (e) | |
63556fc6 | 133 | (with-current-buffer nntp-server-buffer |
23f87bed MB |
134 | (erase-buffer) |
135 | (dolist (article articles) | |
136 | (if (setq e (assq article nnrss-group-data)) | |
137 | (insert (number-to-string (car e)) "\t" ;; number | |
91472578 MB |
138 | ;; subject |
139 | (or (nth 3 e) "") | |
140 | "\t" | |
141 | ;; from | |
142 | (or (nth 4 e) "(nobody)") | |
143 | "\t" | |
144 | ;; date | |
23f87bed | 145 | (or (nth 5 e) "") |
91472578 MB |
146 | "\t" |
147 | ;; id | |
23f87bed | 148 | (format "<%d@%s.nnrss>" (car e) group) |
91472578 MB |
149 | "\t" |
150 | ;; refs | |
151 | "\t" | |
152 | ;; chars | |
153 | "-1" "\t" | |
154 | ;; lines | |
155 | "-1" "\t" | |
156 | ;; Xref | |
157 | "" "\t" | |
23f87bed MB |
158 | (if (and (nth 6 e) |
159 | (memq nnrss-description-field | |
160 | nnmail-extra-headers)) | |
161 | (concat (symbol-name nnrss-description-field) | |
162 | ": " | |
163 | (nnrss-format-string (nth 6 e)) | |
164 | "\t") | |
165 | "") | |
166 | (if (and (nth 2 e) | |
167 | (memq nnrss-url-field | |
168 | nnmail-extra-headers)) | |
169 | (concat (symbol-name nnrss-url-field) | |
170 | ": " | |
171 | (nnrss-format-string (nth 2 e)) | |
172 | "\t") | |
173 | "") | |
174 | "\n"))))) | |
175 | 'nov) | |
176 | ||
286c4fc2 | 177 | (deffoo nnrss-request-group (group &optional server dont-check info) |
91472578 MB |
178 | (setq group (nnrss-decode-group-name group)) |
179 | (nnheader-message 6 "nnrss: Requesting %s..." group) | |
23f87bed | 180 | (nnrss-possibly-change-group group server) |
91472578 MB |
181 | (prog1 |
182 | (if dont-check | |
183 | t | |
184 | (nnrss-check-group group server) | |
185 | (nnheader-report 'nnrss "Opened group %s" group) | |
186 | (nnheader-insert | |
187 | "211 %d %d %d %s\n" nnrss-group-max nnrss-group-min nnrss-group-max | |
188 | (prin1-to-string group) | |
189 | t)) | |
190 | (nnheader-message 6 "nnrss: Requesting %s...done" group))) | |
23f87bed MB |
191 | |
192 | (deffoo nnrss-close-group (group &optional server) | |
193 | t) | |
194 | ||
195 | (deffoo nnrss-request-article (article &optional group server buffer) | |
91472578 MB |
196 | (setq group (nnrss-decode-group-name group)) |
197 | (when (stringp article) | |
198 | (setq article (if (string-match "\\`<\\([0-9]+\\)@" article) | |
199 | (string-to-number (match-string 1 article)) | |
200 | 0))) | |
23f87bed MB |
201 | (nnrss-possibly-change-group group server) |
202 | (let ((e (assq article nnrss-group-data)) | |
23f87bed MB |
203 | (nntp-server-buffer (or buffer nntp-server-buffer)) |
204 | post err) | |
205 | (when e | |
91472578 MB |
206 | (with-current-buffer nntp-server-buffer |
207 | (erase-buffer) | |
208 | (if group | |
209 | (insert "Newsgroups: " group "\n")) | |
210 | (if (nth 3 e) | |
211 | (insert "Subject: " (nth 3 e) "\n")) | |
212 | (if (nth 4 e) | |
213 | (insert "From: " (nth 4 e) "\n")) | |
214 | (if (nth 5 e) | |
215 | (insert "Date: " (nnrss-format-string (nth 5 e)) "\n")) | |
216 | (let ((header (buffer-string)) | |
7dafe00b | 217 | (text (nth 6 e)) |
91472578 | 218 | (link (nth 2 e)) |
31640842 | 219 | (enclosure (nth 7 e)) |
d3a597b7 | 220 | (comments (nth 8 e)) |
91472578 MB |
221 | (rfc2047-header-encoding-alist |
222 | (if (mm-coding-system-p 'utf-8) | |
223 | (cons '("Newsgroups" . utf-8) | |
224 | rfc2047-header-encoding-alist) | |
225 | rfc2047-header-encoding-alist)) | |
7dafe00b | 226 | rfc2047-encode-encoded-words body fn) |
d3a597b7 | 227 | (when (or text link enclosure comments) |
91472578 MB |
228 | (insert "\n") |
229 | (insert "<#multipart type=alternative>\n" | |
230 | "<#part type=\"text/plain\">\n") | |
231 | (setq body (point)) | |
31640842 | 232 | (when text |
7dafe00b MB |
233 | (insert text) |
234 | (goto-char body) | |
ab388ec4 KY |
235 | (while (re-search-forward "\n+" nil t) |
236 | (replace-match " ")) | |
237 | (goto-char body) | |
238 | ;; See `nnrss-check-group', which inserts "<br /><br />". | |
239 | (when (search-forward "<br /><br />" nil t) | |
240 | (if (eobp) | |
241 | (replace-match "\n") | |
242 | (replace-match "\n\n"))) | |
243 | (unless (eobp) | |
244 | (let ((fill-column (default-value 'fill-column)) | |
245 | (window (get-buffer-window nntp-server-buffer))) | |
246 | (when window | |
247 | (setq fill-column | |
248 | (max 1 (/ (* (window-width window) 7) 8)))) | |
249 | (fill-region (point) (point-max)) | |
250 | (goto-char (point-max)) | |
251 | ;; XEmacs version of `fill-region' inserts newline. | |
252 | (unless (bolp) | |
253 | (insert "\n")))) | |
31640842 MB |
254 | (when (or link enclosure) |
255 | (insert "\n"))) | |
256 | (when link | |
257 | (insert link "\n")) | |
258 | (when enclosure | |
259 | (insert (car enclosure) " " | |
260 | (nth 2 enclosure) " " | |
261 | (nth 3 enclosure) "\n")) | |
d3a597b7 MB |
262 | (when comments |
263 | (insert comments "\n")) | |
91472578 MB |
264 | (setq body (buffer-substring body (point))) |
265 | (insert "<#/part>\n" | |
266 | "<#part type=\"text/html\">\n" | |
267 | "<html><head></head><body>\n") | |
268 | (when text | |
269 | (insert text "\n")) | |
270 | (when link | |
271 | (insert "<p><a href=\"" link "\">link</a></p>\n")) | |
31640842 MB |
272 | (when enclosure |
273 | (insert "<p><a href=\"" (car enclosure) "\">" | |
274 | (cadr enclosure) "</a> " (nth 2 enclosure) | |
275 | " " (nth 3 enclosure) "</p>\n")) | |
d3a597b7 MB |
276 | (when comments |
277 | (insert "<p><a href=\"" comments "\">comments</a></p>\n")) | |
91472578 MB |
278 | (insert "</body></html>\n" |
279 | "<#/part>\n" | |
280 | "<#/multipart>\n")) | |
281 | (condition-case nil | |
554a69b8 KY |
282 | ;; Allow `mml-to-mime' to generate MIME article without |
283 | ;; making inquiry to a user for unknown encoding. | |
284 | (let ((mml-confirmation-set | |
285 | (cons 'unknown-encoding mml-confirmation-set))) | |
286 | (mml-to-mime)) | |
91472578 MB |
287 | (error |
288 | (erase-buffer) | |
289 | (insert header | |
290 | "Content-Type: text/plain; charset=gnus-decoded\n" | |
291 | "Content-Transfer-Encoding: 8bit\n\n" | |
292 | body) | |
293 | (nnheader-message | |
294 | 3 "Warning - there might be invalid characters")))) | |
295 | (goto-char (point-min)) | |
296 | (search-forward "\n\n") | |
297 | (forward-line -1) | |
298 | (insert (format "Message-ID: <%d@%s.nnrss>\n" | |
299 | (car e) | |
300 | (let ((rfc2047-encoding-type 'mime) | |
301 | rfc2047-encode-max-chars) | |
302 | (rfc2047-encode-string | |
303 | (gnus-replace-in-string group "[\t\n ]+" "_"))))) | |
304 | (when nnrss-content-function | |
305 | (funcall nnrss-content-function e group article)))) | |
23f87bed MB |
306 | (cond |
307 | (err | |
308 | (nnheader-report 'nnrss err)) | |
309 | ((not e) | |
310 | (nnheader-report 'nnrss "no such id: %d" article)) | |
311 | (t | |
312 | (nnheader-report 'nnrss "article %s retrieved" (car e)) | |
313 | ;; we return the article number. | |
314 | (cons nnrss-group (car e)))))) | |
315 | ||
23f87bed MB |
316 | (deffoo nnrss-open-server (server &optional defs connectionless) |
317 | (nnrss-read-server-data server) | |
318 | (nnoo-change-server 'nnrss server defs) | |
319 | t) | |
320 | ||
321 | (deffoo nnrss-request-expire-articles | |
322 | (articles group &optional server force) | |
91472578 | 323 | (setq group (nnrss-decode-group-name group)) |
23f87bed MB |
324 | (nnrss-possibly-change-group group server) |
325 | (let (e days not-expirable changed) | |
326 | (dolist (art articles) | |
327 | (if (and (setq e (assq art nnrss-group-data)) | |
328 | (nnmail-expired-article-p | |
329 | group | |
330 | (if (listp (setq days (nth 1 e))) days | |
331 | (days-to-time (- days (time-to-days '(0 0))))) | |
332 | force)) | |
333 | (setq nnrss-group-data (delq e nnrss-group-data) | |
334 | changed t) | |
335 | (push art not-expirable))) | |
336 | (if changed | |
337 | (nnrss-save-group-data group server)) | |
338 | not-expirable)) | |
339 | ||
340 | (deffoo nnrss-request-delete-group (group &optional force server) | |
91472578 | 341 | (setq group (nnrss-decode-group-name group)) |
23f87bed | 342 | (nnrss-possibly-change-group group server) |
91472578 MB |
343 | (let (elem) |
344 | ;; There may be two or more entries in `nnrss-group-alist' since | |
345 | ;; this function didn't delete them formerly. | |
346 | (while (setq elem (assoc group nnrss-group-alist)) | |
347 | (setq nnrss-group-alist (delq elem nnrss-group-alist)))) | |
23f87bed MB |
348 | (setq nnrss-server-data |
349 | (delq (assoc group nnrss-server-data) nnrss-server-data)) | |
350 | (nnrss-save-server-data server) | |
91472578 | 351 | (ignore-errors |
01c52d31 MB |
352 | (let ((file-name-coding-system nnmail-pathname-coding-system)) |
353 | (delete-file (nnrss-make-filename group server)))) | |
23f87bed MB |
354 | t) |
355 | ||
356 | (deffoo nnrss-request-list-newsgroups (&optional server) | |
357 | (nnrss-possibly-change-group nil server) | |
63556fc6 | 358 | (with-current-buffer nntp-server-buffer |
23f87bed MB |
359 | (erase-buffer) |
360 | (dolist (elem nnrss-group-alist) | |
361 | (if (third elem) | |
362 | (insert (car elem) "\t" (third elem) "\n")))) | |
363 | t) | |
364 | ||
74769e8b | 365 | (deffoo nnrss-retrieve-groups (groups &optional server) |
74769e8b | 366 | (dolist (group groups) |
61b1af82 | 367 | (nnrss-possibly-change-group group server) |
74769e8b | 368 | (nnrss-check-group group server)) |
63556fc6 | 369 | (with-current-buffer nntp-server-buffer |
74769e8b LMI |
370 | (erase-buffer) |
371 | (dolist (group groups) | |
372 | (let ((elem (assoc group nnrss-server-data))) | |
373 | (insert (format "%S %s 1 y\n" group (or (cadr elem) 0))))) | |
374 | 'active)) | |
375 | ||
23f87bed MB |
376 | (nnoo-define-skeleton nnrss) |
377 | ||
378 | ;;; Internal functions | |
379 | (eval-when-compile (defun xml-rpc-method-call (&rest args))) | |
91472578 MB |
380 | |
381 | (defun nnrss-get-encoding () | |
382 | "Return an encoding attribute specified in the current xml contents. | |
383 | If `nnrss-compatible-encoding-alist' specifies the compatible encoding, | |
384 | it is used instead. If the xml contents doesn't specify the encoding, | |
385 | return `utf-8' which is the default encoding for xml if it is available, | |
386 | otherwise return nil." | |
387 | (goto-char (point-min)) | |
388 | (if (re-search-forward | |
01c52d31 | 389 | "<\\?[^>]*encoding=\\(?:\"\\([^\">]+\\)\"\\|'\\([^'>]+\\)'\\)" |
91472578 | 390 | nil t) |
01c52d31 MB |
391 | (let ((encoding (intern (downcase (or (match-string 1) |
392 | (match-string 2)))))) | |
91472578 MB |
393 | (or |
394 | (mm-coding-system-p (cdr (assq encoding | |
395 | nnrss-compatible-encoding-alist))) | |
396 | (mm-coding-system-p encoding) | |
397 | (mm-coding-system-p (car (rassq encoding | |
398 | nnrss-compatible-encoding-alist))))) | |
399 | (mm-coding-system-p 'utf-8))) | |
400 | ||
0f7cbeb9 GM |
401 | (declare-function w3-parse-buffer "ext:w3-parse" (&optional buff)) |
402 | ||
23f87bed | 403 | (defun nnrss-fetch (url &optional local) |
91472578 MB |
404 | "Fetch URL and put it in a the expected Lisp structure." |
405 | (mm-with-unibyte-buffer | |
bff3818b | 406 | ;;some versions of url.el need this to close the connection quickly |
91472578 | 407 | (let (cs xmlform htmlform) |
23f87bed MB |
408 | ;; bit o' work necessary for w3 pre-cvs and post-cvs |
409 | (if local | |
410 | (let ((coding-system-for-read 'binary)) | |
411 | (insert-file-contents url)) | |
91472578 MB |
412 | ;; FIXME: shouldn't binding `coding-system-for-read' be moved |
413 | ;; to `mm-url-insert'? | |
414 | (let ((coding-system-for-read 'binary)) | |
7dafe00b MB |
415 | (condition-case err |
416 | (mm-url-insert url) | |
417 | (error (if (or debug-on-quit debug-on-error) | |
418 | (signal (car err) (cdr err)) | |
419 | (message "nnrss: Failed to fetch %s" url)))))) | |
91472578 MB |
420 | (nnheader-remove-cr-followed-by-lf) |
421 | ;; Decode text according to the encoding attribute. | |
422 | (when (setq cs (nnrss-get-encoding)) | |
11e95b02 MB |
423 | (insert (prog1 |
424 | (mm-decode-coding-string (buffer-string) cs) | |
425 | (erase-buffer) | |
426 | (mm-enable-multibyte)))) | |
91472578 MB |
427 | (goto-char (point-min)) |
428 | ||
429 | ;; Because xml-parse-region can't deal with anything that isn't | |
430 | ;; xml and w3-parse-buffer can't deal with some xml, we have to | |
431 | ;; parse with xml-parse-region first and, if that fails, parse | |
432 | ;; with w3-parse-buffer. Yuck. Eventually, someone should find out | |
433 | ;; why w3-parse-buffer fails to parse some well-formed xml and | |
434 | ;; fix it. | |
435 | ||
436 | (condition-case err1 | |
437 | (setq xmlform (xml-parse-region (point-min) (point-max))) | |
438 | (error | |
439 | (condition-case err2 | |
440 | (setq htmlform (caddar (w3-parse-buffer | |
441 | (current-buffer)))) | |
442 | (error | |
443 | (message "\ | |
444 | nnrss: %s: Not valid XML %s and w3-parse doesn't work %s" | |
445 | url err1 err2))))) | |
446 | (if htmlform | |
447 | htmlform | |
448 | xmlform)))) | |
23f87bed MB |
449 | |
450 | (defun nnrss-possibly-change-group (&optional group server) | |
451 | (when (and server | |
452 | (not (nnrss-server-opened server))) | |
453 | (nnrss-open-server server)) | |
454 | (when (and group (not (equal group nnrss-group))) | |
455 | (nnrss-read-group-data group server) | |
456 | (setq nnrss-group group))) | |
457 | ||
8abf1b22 | 458 | (autoload 'timezone-parse-date "timezone") |
7dafe00b MB |
459 | |
460 | (defun nnrss-normalize-date (date) | |
461 | "Return a date string of DATE in the RFC822 style. | |
462 | This function handles the ISO 8601 date format described in | |
0b10e437 | 463 | URL `http://www.w3.org/TR/NOTE-datetime', and also the RFC822 style |
7dafe00b | 464 | which RSS 2.0 allows." |
4079589f MB |
465 | (let (case-fold-search vector year month day time zone cts given) |
466 | (cond ((null date)) ; do nothing for this case | |
467 | ;; if the date is just digits (unix time stamp): | |
468 | ((string-match "^[0-9]+$" date) | |
469 | (setq given (seconds-to-time (string-to-number date)))) | |
7dafe00b MB |
470 | ;; RFC822 |
471 | ((string-match " [0-9]+ " date) | |
472 | (setq vector (timezone-parse-date date) | |
473 | year (string-to-number (aref vector 0))) | |
474 | (when (>= year 1969) | |
475 | (setq month (string-to-number (aref vector 1)) | |
476 | day (string-to-number (aref vector 2))) | |
477 | (unless (>= (length (setq time (aref vector 3))) 3) | |
478 | (setq time "00:00:00")) | |
479 | (when (and (setq zone (aref vector 4)) | |
480 | (not (string-match "\\`[A-Z+-]" zone))) | |
481 | (setq zone nil)))) | |
482 | ;; ISO 8601 | |
483 | ((string-match | |
484 | (eval-when-compile | |
485 | (concat | |
486 | ;; 1. year | |
487 | "\\(199[0-9]\\|20[0-9][0-9]\\)" | |
01c52d31 MB |
488 | "\\(?:-" |
489 | ;; 2. month | |
7dafe00b | 490 | "\\([01][0-9]\\)" |
01c52d31 MB |
491 | "\\(?:-" |
492 | ;; 3. day | |
7dafe00b | 493 | "\\([0-3][0-9]\\)" |
01c52d31 MB |
494 | "\\)?\\)?\\(?:T" |
495 | ;; 4. hh:mm | |
7dafe00b | 496 | "\\([012][0-9]:[0-5][0-9]\\)" |
01c52d31 MB |
497 | "\\(?:" |
498 | ;; 5. :ss | |
7dafe00b | 499 | "\\(:[0-5][0-9]\\)" |
01c52d31 MB |
500 | "\\(?:\\.[0-9]+\\)?\\)?\\)?" |
501 | ;; 6+7,8,9. zone | |
502 | "\\(?:\\(?:\\([+-][012][0-9]\\):\\([0-5][0-9]\\)\\)" | |
7dafe00b MB |
503 | "\\|\\([+-][012][0-9][0-5][0-9]\\)" |
504 | "\\|\\(Z\\)\\)?")) | |
505 | date) | |
506 | (setq year (string-to-number (match-string 1 date)) | |
01c52d31 MB |
507 | month (string-to-number (or (match-string 2 date) "1")) |
508 | day (string-to-number (or (match-string 3 date) "1")) | |
509 | time (if (match-beginning 5) | |
510 | (substring date (match-beginning 4) (match-end 5)) | |
511 | (concat (or (match-string 4 date) "00:00") ":00")) | |
512 | zone (cond ((match-beginning 6) | |
513 | (concat (match-string 6 date) | |
514 | (match-string 7 date))) | |
515 | ((match-beginning 9) ;; Z | |
7dafe00b MB |
516 | "+0000") |
517 | (t ;; nil if zone is not provided. | |
01c52d31 | 518 | (match-string 8 date)))))) |
7dafe00b MB |
519 | (if month |
520 | (progn | |
521 | (setq cts (current-time-string (encode-time 0 0 0 day month year))) | |
522 | (format "%s, %02d %s %04d %s%s" | |
523 | (substring cts 0 3) day (substring cts 4 7) year time | |
524 | (if zone | |
525 | (concat " " zone) | |
526 | ""))) | |
4079589f | 527 | (message-make-date given)))) |
7dafe00b | 528 | |
23f87bed MB |
529 | ;;; data functions |
530 | ||
531 | (defun nnrss-read-server-data (server) | |
532 | (setq nnrss-server-data nil) | |
01c52d31 MB |
533 | (let ((file (nnrss-make-filename "nnrss" server)) |
534 | (file-name-coding-system nnmail-pathname-coding-system)) | |
23f87bed | 535 | (when (file-exists-p file) |
0c43b6f8 | 536 | (load file nil t t)))) |
23f87bed MB |
537 | |
538 | (defun nnrss-save-server-data (server) | |
539 | (gnus-make-directory nnrss-directory) | |
91472578 MB |
540 | (let ((coding-system-for-write nnrss-file-coding-system) |
541 | (file-name-coding-system nnmail-pathname-coding-system)) | |
542 | (with-temp-file (nnrss-make-filename "nnrss" server) | |
543 | (insert (format ";; -*- coding: %s; -*-\n" | |
544 | nnrss-file-coding-system)) | |
545 | (gnus-prin1 `(setq nnrss-group-alist ',nnrss-group-alist)) | |
546 | (insert "\n") | |
547 | (gnus-prin1 `(setq nnrss-server-data ',nnrss-server-data))))) | |
23f87bed MB |
548 | |
549 | (defun nnrss-read-group-data (group server) | |
550 | (setq nnrss-group-data nil) | |
01c52d31 MB |
551 | (if (hash-table-p nnrss-group-hashtb) |
552 | (clrhash nnrss-group-hashtb) | |
553 | (setq nnrss-group-hashtb (make-hash-table :test 'equal))) | |
23f87bed MB |
554 | (let ((pair (assoc group nnrss-server-data))) |
555 | (setq nnrss-group-max (or (cadr pair) 0)) | |
556 | (setq nnrss-group-min (+ nnrss-group-max 1))) | |
01c52d31 MB |
557 | (let ((file (nnrss-make-filename group server)) |
558 | (file-name-coding-system nnmail-pathname-coding-system)) | |
23f87bed | 559 | (when (file-exists-p file) |
0c43b6f8 | 560 | (load file nil t t) |
23f87bed | 561 | (dolist (e nnrss-group-data) |
01c52d31 | 562 | (puthash (nth 9 e) t nnrss-group-hashtb) |
91472578 MB |
563 | (when (and (car e) (> nnrss-group-min (car e))) |
564 | (setq nnrss-group-min (car e))) | |
565 | (when (and (car e) (< nnrss-group-max (car e))) | |
566 | (setq nnrss-group-max (car e))))))) | |
23f87bed MB |
567 | |
568 | (defun nnrss-save-group-data (group server) | |
569 | (gnus-make-directory nnrss-directory) | |
91472578 MB |
570 | (let ((coding-system-for-write nnrss-file-coding-system) |
571 | (file-name-coding-system nnmail-pathname-coding-system)) | |
572 | (with-temp-file (nnrss-make-filename group server) | |
573 | (insert (format ";; -*- coding: %s; -*-\n" | |
574 | nnrss-file-coding-system)) | |
575 | (gnus-prin1 `(setq nnrss-group-data ',nnrss-group-data))))) | |
576 | ||
577 | (defun nnrss-make-filename (name server) | |
578 | (expand-file-name | |
579 | (nnrss-translate-file-chars | |
580 | (concat name | |
581 | (and server | |
582 | (not (equal server "")) | |
583 | "-") | |
584 | server | |
585 | ".el")) | |
586 | nnrss-directory)) | |
587 | ||
588 | (gnus-add-shutdown 'nnrss-close 'gnus) | |
589 | ||
590 | (defun nnrss-close () | |
591 | "Clear internal nnrss variables." | |
592 | (setq nnrss-group-data nil | |
593 | nnrss-server-data nil | |
594 | nnrss-group-hashtb nil | |
595 | nnrss-group-alist nil)) | |
23f87bed MB |
596 | |
597 | ;;; URL interface | |
598 | ||
599 | (defun nnrss-no-cache (url) | |
600 | "") | |
601 | ||
602 | (defun nnrss-insert-w3 (url) | |
603 | (mm-with-unibyte-current-buffer | |
7dafe00b MB |
604 | (condition-case err |
605 | (mm-url-insert url) | |
606 | (error (if (or debug-on-quit debug-on-error) | |
607 | (signal (car err) (cdr err)) | |
608 | (message "nnrss: Failed to fetch %s" url)))))) | |
23f87bed | 609 | |
91472578 | 610 | (defun nnrss-decode-entities-string (string) |
23f87bed | 611 | (if string |
91472578 | 612 | (mm-with-multibyte-buffer |
23f87bed MB |
613 | (insert string) |
614 | (mm-url-decode-entities-nbsp) | |
615 | (buffer-string)))) | |
616 | ||
617 | (defalias 'nnrss-insert 'nnrss-insert-w3) | |
618 | ||
91472578 MB |
619 | (defun nnrss-mime-encode-string (string) |
620 | (mm-with-multibyte-buffer | |
621 | (insert string) | |
622 | (mm-url-decode-entities-nbsp) | |
623 | (goto-char (point-min)) | |
624 | (while (re-search-forward "[\t\n ]+" nil t) | |
625 | (replace-match " ")) | |
626 | (goto-char (point-min)) | |
627 | (skip-chars-forward " ") | |
628 | (delete-region (point-min) (point)) | |
629 | (goto-char (point-max)) | |
630 | (skip-chars-forward " ") | |
631 | (delete-region (point) (point-max)) | |
632 | (let ((rfc2047-encoding-type 'mime) | |
633 | rfc2047-encode-max-chars) | |
634 | (rfc2047-encode-region (point-min) (point-max))) | |
635 | (goto-char (point-min)) | |
636 | (while (search-forward "\n" nil t) | |
d355a0b7 | 637 | (delete-char -1)) |
91472578 MB |
638 | (buffer-string))) |
639 | ||
23f87bed | 640 | ;;; Snarf functions |
01c52d31 | 641 | (defun nnrss-make-hash-index (item) |
5b51650c | 642 | (gnus-message 9 "nnrss: Making hash index of %s" (gnus-prin1-to-string item)) |
01c52d31 MB |
643 | (setq item (gnus-remove-if |
644 | (lambda (field) | |
645 | (when (listp field) | |
646 | (memq (car field) nnrss-ignore-article-fields))) | |
647 | item)) | |
648 | (md5 (gnus-prin1-to-string item) | |
649 | nil nil | |
650 | nnrss-file-coding-system)) | |
651 | ||
23f87bed | 652 | (defun nnrss-check-group (group server) |
7dafe00b | 653 | (let (file xml subject url extra changed author date feed-subject |
01c52d31 MB |
654 | enclosure comments rss-ns rdf-ns content-ns dc-ns |
655 | hash-index) | |
23f87bed MB |
656 | (if (and nnrss-use-local |
657 | (file-exists-p (setq file (expand-file-name | |
658 | (nnrss-translate-file-chars | |
659 | (concat group ".xml")) | |
660 | nnrss-directory)))) | |
661 | (setq xml (nnrss-fetch file t)) | |
662 | (setq url (or (nth 2 (assoc group nnrss-server-data)) | |
663 | (second (assoc group nnrss-group-alist)))) | |
664 | (unless url | |
665 | (setq url | |
91472578 MB |
666 | (cdr |
667 | (assoc 'href | |
668 | (nnrss-discover-feed | |
669 | (read-string | |
670 | (format "URL to search for %s: " group) "http://"))))) | |
23f87bed MB |
671 | (let ((pair (assoc group nnrss-server-data))) |
672 | (if pair | |
673 | (setcdr (cdr pair) (list url)) | |
674 | (push (list group nnrss-group-max url) nnrss-server-data))) | |
675 | (setq changed t)) | |
676 | (setq xml (nnrss-fetch url))) | |
23f87bed MB |
677 | (setq dc-ns (nnrss-get-namespace-prefix xml "http://purl.org/dc/elements/1.1/") |
678 | rdf-ns (nnrss-get-namespace-prefix xml "http://www.w3.org/1999/02/22-rdf-syntax-ns#") | |
679 | rss-ns (nnrss-get-namespace-prefix xml "http://purl.org/rss/1.0/") | |
680 | content-ns (nnrss-get-namespace-prefix xml "http://purl.org/rss/1.0/modules/content/")) | |
681 | (dolist (item (nreverse (nnrss-find-el (intern (concat rss-ns "item")) xml))) | |
682 | (when (and (listp item) | |
91472578 | 683 | (string= (concat rss-ns "item") (car item)) |
01c52d31 MB |
684 | (progn (setq hash-index (nnrss-make-hash-index item)) |
685 | (not (gethash hash-index nnrss-group-hashtb)))) | |
23f87bed | 686 | (setq subject (nnrss-node-text rss-ns 'title item)) |
01c52d31 MB |
687 | (setq url (nnrss-decode-entities-string |
688 | (nnrss-node-text rss-ns 'link (cddr item)))) | |
689 | (setq extra (or (nnrss-node-text content-ns 'encoded item) | |
23f87bed | 690 | (nnrss-node-text rss-ns 'description item))) |
7dafe00b MB |
691 | (if (setq feed-subject (nnrss-node-text dc-ns 'subject item)) |
692 | (setq extra (concat feed-subject "<br /><br />" extra))) | |
23f87bed MB |
693 | (setq author (or (nnrss-node-text rss-ns 'author item) |
694 | (nnrss-node-text dc-ns 'creator item) | |
695 | (nnrss-node-text dc-ns 'contributor item))) | |
7dafe00b MB |
696 | (setq date (nnrss-normalize-date |
697 | (or (nnrss-node-text dc-ns 'date item) | |
698 | (nnrss-node-text rss-ns 'pubDate item)))) | |
d3a597b7 | 699 | (setq comments (nnrss-node-text rss-ns 'comments item)) |
31640842 MB |
700 | (when (setq enclosure (cadr (assq (intern (concat rss-ns "enclosure")) item))) |
701 | (let ((url (cdr (assq 'url enclosure))) | |
702 | (len (cdr (assq 'length enclosure))) | |
703 | (type (cdr (assq 'type enclosure))) | |
704 | (name)) | |
705 | (setq len | |
706 | (if (and len (integerp (setq len (string-to-number len)))) | |
707 | ;; actually already in `ls-lisp-format-file-size' but | |
708 | ;; probably not worth to require it for one function | |
709 | (do ((size (/ len 1.0) (/ size 1024.0)) | |
710 | (post-fixes (list "" "k" "M" "G" "T" "P" "E") | |
711 | (cdr post-fixes))) | |
712 | ((< size 1024) | |
713 | (format "%.1f%s" size (car post-fixes)))) | |
714 | "0")) | |
715 | (setq url (or url "")) | |
716 | (setq name (if (string-match "/\\([^/]*\\)$" url) | |
717 | (match-string 1 url) | |
718 | "file")) | |
719 | (setq type (or type "")) | |
720 | (setq enclosure (list url name len type)))) | |
23f87bed MB |
721 | (push |
722 | (list | |
723 | (incf nnrss-group-max) | |
724 | (current-time) | |
725 | url | |
91472578 MB |
726 | (and subject (nnrss-mime-encode-string subject)) |
727 | (and author (nnrss-mime-encode-string author)) | |
23f87bed | 728 | date |
31640842 | 729 | (and extra (nnrss-decode-entities-string extra)) |
d3a597b7 | 730 | enclosure |
01c52d31 MB |
731 | comments |
732 | hash-index) | |
23f87bed | 733 | nnrss-group-data) |
01c52d31 | 734 | (puthash hash-index t nnrss-group-hashtb) |
91472578 MB |
735 | (setq changed t)) |
736 | (setq extra nil)) | |
23f87bed MB |
737 | (when changed |
738 | (nnrss-save-group-data group server) | |
739 | (let ((pair (assoc group nnrss-server-data))) | |
740 | (if pair | |
741 | (setcar (cdr pair) nnrss-group-max) | |
742 | (push (list group nnrss-group-max) nnrss-server-data))) | |
743 | (nnrss-save-server-data server)))) | |
744 | ||
0f7cbeb9 GM |
745 | (declare-function gnus-group-make-rss-group "gnus-group" (&optional url)) |
746 | ||
91472578 MB |
747 | (defun nnrss-opml-import (opml-file) |
748 | "OPML subscriptions import. | |
749 | Read the file and attempt to subscribe to each Feed in the file." | |
750 | (interactive "fImport file: ") | |
c4288669 MB |
751 | (mapc |
752 | (lambda (node) | |
753 | (let ((xmlurl (cdr (assq 'xmlUrl (cadr node))))) | |
754 | (when (and xmlurl | |
755 | (not (string-match "\\`[\t ]*\\'" xmlurl)) | |
756 | (prog1 | |
757 | (y-or-n-p (format "Subscribe to %s " xmlurl)) | |
758 | (message ""))) | |
759 | (condition-case err | |
760 | (progn | |
761 | (gnus-group-make-rss-group xmlurl) | |
762 | (forward-line 1)) | |
763 | (error | |
764 | (message | |
765 | "Failed to subscribe to %s (%s); type any key to continue: " | |
766 | xmlurl | |
767 | (error-message-string err)) | |
768 | (let ((echo-keystrokes 0)) | |
769 | (read-char))))))) | |
91472578 | 770 | (nnrss-find-el 'outline |
c4288669 MB |
771 | (mm-with-multibyte-buffer |
772 | (insert-file-contents opml-file) | |
773 | (xml-parse-region (point-min) (point-max)))))) | |
91472578 MB |
774 | |
775 | (defun nnrss-opml-export () | |
776 | "OPML subscription export. | |
777 | Export subscriptions to a buffer in OPML Format." | |
778 | (interactive) | |
779 | (with-current-buffer (get-buffer-create "*OPML Export*") | |
780 | (mm-set-buffer-file-coding-system 'utf-8) | |
781 | (insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
782 | "<!-- OPML generated by Emacs Gnus' nnrss.el -->\n" | |
783 | "<opml version=\"1.1\">\n" | |
784 | " <head>\n" | |
785 | " <title>mySubscriptions</title>\n" | |
786 | " <dateCreated>" (format-time-string "%a, %d %b %Y %T %z") | |
787 | "</dateCreated>\n" | |
788 | " <ownerEmail>" user-mail-address "</ownerEmail>\n" | |
789 | " <ownerName>" (user-full-name) "</ownerName>\n" | |
790 | " </head>\n" | |
791 | " <body>\n") | |
792 | (dolist (sub nnrss-group-alist) | |
793 | (insert " <outline text=\"" (car sub) | |
794 | "\" xmlUrl=\"" (cadr sub) "\"/>\n")) | |
795 | (insert " </body>\n" | |
796 | "</opml>\n")) | |
797 | (pop-to-buffer "*OPML Export*") | |
798 | (when (fboundp 'sgml-mode) | |
799 | (sgml-mode))) | |
800 | ||
23f87bed MB |
801 | (defun nnrss-generate-download-script () |
802 | "Generate a download script in the current buffer. | |
803 | It is useful when `(setq nnrss-use-local t)'." | |
804 | (interactive) | |
805 | (insert "#!/bin/sh\n") | |
806 | (insert "WGET=wget\n") | |
807 | (insert "RSSDIR='" (expand-file-name nnrss-directory) "'\n") | |
808 | (dolist (elem nnrss-server-data) | |
809 | (let ((url (or (nth 2 elem) | |
810 | (second (assoc (car elem) nnrss-group-alist))))) | |
811 | (insert "$WGET -q -O \"$RSSDIR\"/'" | |
812 | (nnrss-translate-file-chars (concat (car elem) ".xml")) | |
813 | "' '" url "'\n")))) | |
814 | ||
815 | (defun nnrss-translate-file-chars (name) | |
816 | (let ((nnheader-file-name-translation-alist | |
817 | (append nnheader-file-name-translation-alist '((?' . ?_))))) | |
818 | (nnheader-translate-file-chars name))) | |
819 | ||
23f87bed MB |
820 | (defun nnrss-node-text (namespace local-name element) |
821 | (let* ((node (assq (intern (concat namespace (symbol-name local-name))) | |
822 | element)) | |
823 | (text (if (and node (listp node)) | |
824 | (nnrss-node-just-text node) | |
825 | node)) | |
ff5e68bf MB |
826 | (cleaned-text (if text |
827 | (gnus-replace-in-string | |
828 | (gnus-replace-in-string | |
829 | text "^[\000-\037\177]+\\|^ +\\| +$" "") | |
830 | "\r\n" "\n")))) | |
23f87bed MB |
831 | (if (string-equal "" cleaned-text) |
832 | nil | |
833 | cleaned-text))) | |
834 | ||
835 | (defun nnrss-node-just-text (node) | |
836 | (if (and node (listp node)) | |
837 | (mapconcat 'nnrss-node-just-text (cddr node) " ") | |
838 | node)) | |
839 | ||
840 | (defun nnrss-find-el (tag data &optional found-list) | |
91472578 MB |
841 | "Find the all matching elements in the data. |
842 | Careful with this on large documents!" | |
843 | (when (consp data) | |
844 | (dolist (bit data) | |
845 | (when (car-safe bit) | |
846 | (when (equal tag (car bit)) | |
847 | ;; Old xml.el may return a list of string. | |
848 | (when (and (consp (caddr bit)) | |
849 | (stringp (caaddr bit))) | |
850 | (setcar (cddr bit) (caaddr bit))) | |
851 | (setq found-list | |
852 | (append found-list | |
853 | (list bit)))) | |
854 | (if (and (consp (car-safe (caddr bit))) | |
855 | (not (stringp (caddr bit)))) | |
856 | (setq found-list | |
857 | (append found-list | |
858 | (nnrss-find-el | |
859 | tag (caddr bit)))) | |
860 | (setq found-list | |
861 | (append found-list | |
862 | (nnrss-find-el | |
863 | tag (cddr bit)))))))) | |
23f87bed MB |
864 | found-list) |
865 | ||
866 | (defun nnrss-rsslink-p (el) | |
867 | "Test if the element we are handed is an RSS autodiscovery link." | |
868 | (and (eq (car-safe el) 'link) | |
869 | (string-equal (cdr (assoc 'rel (cadr el))) "alternate") | |
91472578 | 870 | (or (string-equal (cdr (assoc 'type (cadr el))) |
23f87bed MB |
871 | "application/rss+xml") |
872 | (string-equal (cdr (assoc 'type (cadr el))) "text/xml")))) | |
873 | ||
874 | (defun nnrss-get-rsslinks (data) | |
875 | "Extract the <link> elements that are links to RSS from the parsed data." | |
91472578 | 876 | (delq nil (mapcar |
23f87bed MB |
877 | (lambda (el) |
878 | (if (nnrss-rsslink-p el) el)) | |
879 | (nnrss-find-el 'link data)))) | |
880 | ||
881 | (defun nnrss-extract-hrefs (data) | |
91472578 MB |
882 | "Recursively extract hrefs from a page's source. |
883 | DATA should be the output of `xml-parse-region' or | |
884 | `w3-parse-buffer'." | |
23f87bed MB |
885 | (mapcar (lambda (ahref) |
886 | (cdr (assoc 'href (cadr ahref)))) | |
887 | (nnrss-find-el 'a data))) | |
888 | ||
91472578 | 889 | (defmacro nnrss-match-macro (base-uri item onsite-list offsite-list) |
23f87bed | 890 | `(cond ((or (string-match (concat "^" ,base-uri) ,item) |
91472578 MB |
891 | (not (string-match "://" ,item))) |
892 | (setq ,onsite-list (append ,onsite-list (list ,item)))) | |
893 | (t (setq ,offsite-list (append ,offsite-list (list ,item)))))) | |
23f87bed MB |
894 | |
895 | (defun nnrss-order-hrefs (base-uri hrefs) | |
896 | "Given a list of hrefs, sort them using the following priorities: | |
897 | 1. links ending in .rss | |
898 | 2. links ending in .rdf | |
899 | 3. links ending in .xml | |
900 | 4. links containing the above | |
901 | 5. offsite links | |
902 | ||
903 | BASE-URI is used to determine the location of the links and | |
904 | whether they are `offsite' or `onsite'." | |
905 | (let (rss-onsite-end rdf-onsite-end xml-onsite-end | |
906 | rss-onsite-in rdf-onsite-in xml-onsite-in | |
907 | rss-offsite-end rdf-offsite-end xml-offsite-end | |
01c52d31 | 908 | rss-offsite-in rdf-offsite-in xml-offsite-in) |
91472578 MB |
909 | (dolist (href hrefs) |
910 | (cond ((null href)) | |
911 | ((string-match "\\.rss$" href) | |
912 | (nnrss-match-macro | |
913 | base-uri href rss-onsite-end rss-offsite-end)) | |
914 | ((string-match "\\.rdf$" href) | |
915 | (nnrss-match-macro | |
916 | base-uri href rdf-onsite-end rdf-offsite-end)) | |
917 | ((string-match "\\.xml$" href) | |
918 | (nnrss-match-macro | |
919 | base-uri href xml-onsite-end xml-offsite-end)) | |
920 | ((string-match "rss" href) | |
921 | (nnrss-match-macro | |
922 | base-uri href rss-onsite-in rss-offsite-in)) | |
923 | ((string-match "rdf" href) | |
924 | (nnrss-match-macro | |
925 | base-uri href rdf-onsite-in rdf-offsite-in)) | |
926 | ((string-match "xml" href) | |
927 | (nnrss-match-macro | |
928 | base-uri href xml-onsite-in xml-offsite-in)))) | |
929 | (append | |
23f87bed MB |
930 | rss-onsite-end rdf-onsite-end xml-onsite-end |
931 | rss-onsite-in rdf-onsite-in xml-onsite-in | |
932 | rss-offsite-end rdf-offsite-end xml-offsite-end | |
933 | rss-offsite-in rdf-offsite-in xml-offsite-in))) | |
934 | ||
935 | (defun nnrss-discover-feed (url) | |
936 | "Given a page, find an RSS feed using Mark Pilgrim's | |
66627fa9 | 937 | `ultra-liberal rss locator'." |
23f87bed MB |
938 | |
939 | (let ((parsed-page (nnrss-fetch url))) | |
940 | ||
941 | ;; 1. if this url is the rss, use it. | |
942 | (if (nnrss-rss-p parsed-page) | |
943 | (let ((rss-ns (nnrss-get-namespace-prefix parsed-page "http://purl.org/rss/1.0/"))) | |
944 | (nnrss-rss-title-description rss-ns parsed-page url)) | |
945 | ||
946 | ;; 2. look for the <link rel="alternate" | |
947 | ;; type="application/rss+xml" and use that if it is there. | |
948 | (let ((links (nnrss-get-rsslinks parsed-page))) | |
949 | (if links | |
950 | (let* ((xml (nnrss-fetch | |
951 | (cdr (assoc 'href (cadar links))))) | |
952 | (rss-ns (nnrss-get-namespace-prefix xml "http://purl.org/rss/1.0/"))) | |
953 | (nnrss-rss-title-description rss-ns xml (cdr (assoc 'href (cadar links))))) | |
954 | ||
955 | ;; 3. look for links on the site in the following order: | |
956 | ;; - onsite links ending in .rss, .rdf, or .xml | |
957 | ;; - onsite links containing any of the above | |
958 | ;; - offsite links ending in .rss, .rdf, or .xml | |
959 | ;; - offsite links containing any of the above | |
960 | (let* ((base-uri (progn (string-match ".*://[^/]+/?" url) | |
961 | (match-string 0 url))) | |
91472578 | 962 | (hrefs (nnrss-order-hrefs |
23f87bed MB |
963 | base-uri (nnrss-extract-hrefs parsed-page))) |
964 | (rss-link nil)) | |
91472578 MB |
965 | (while (and (eq rss-link nil) (not (eq hrefs nil))) |
966 | (let ((href-data (nnrss-fetch (car hrefs)))) | |
967 | (if (nnrss-rss-p href-data) | |
968 | (let* ((rss-ns (nnrss-get-namespace-prefix href-data "http://purl.org/rss/1.0/"))) | |
969 | (setq rss-link (nnrss-rss-title-description | |
970 | rss-ns href-data (car hrefs)))) | |
971 | (setq hrefs (cdr hrefs))))) | |
972 | (if rss-link rss-link | |
23f87bed MB |
973 | |
974 | ;; 4. check syndic8 | |
91472578 | 975 | (nnrss-find-rss-via-syndic8 url)))))))) |
23f87bed MB |
976 | |
977 | (defun nnrss-find-rss-via-syndic8 (url) | |
91472578 | 978 | "Query syndic8 for the rss feeds it has for URL." |
23f87bed MB |
979 | (if (not (locate-library "xml-rpc")) |
980 | (progn | |
981 | (message "XML-RPC is not available... not checking Syndic8.") | |
982 | nil) | |
983 | (require 'xml-rpc) | |
984 | (let ((feedid (xml-rpc-method-call | |
985 | "http://www.syndic8.com/xmlrpc.php" | |
986 | 'syndic8.FindSites | |
987 | url))) | |
988 | (when feedid | |
91472578 | 989 | (let* ((feedinfo (xml-rpc-method-call |
23f87bed MB |
990 | "http://www.syndic8.com/xmlrpc.php" |
991 | 'syndic8.GetFeedInfo | |
992 | feedid)) | |
993 | (urllist | |
91472578 | 994 | (delq nil |
23f87bed MB |
995 | (mapcar |
996 | (lambda (listinfo) | |
91472578 | 997 | (if (string-equal |
23f87bed MB |
998 | (cdr (assoc "status" listinfo)) |
999 | "Syndicated") | |
1000 | (cons | |
1001 | (cdr (assoc "sitename" listinfo)) | |
1002 | (list | |
1003 | (cons 'title | |
91472578 | 1004 | (cdr (assoc |
23f87bed MB |
1005 | "sitename" listinfo))) |
1006 | (cons 'href | |
1007 | (cdr (assoc | |
1008 | "dataurl" listinfo))))))) | |
1009 | feedinfo)))) | |
1010 | (if (not (> (length urllist) 1)) | |
1011 | (cdar urllist) | |
1012 | (let ((completion-ignore-case t) | |
91472578 | 1013 | (selection |
23f87bed | 1014 | (mapcar (lambda (listinfo) |
91472578 | 1015 | (cons (cdr (assoc "sitename" listinfo)) |
e9bd5782 | 1016 | (string-to-number |
23f87bed MB |
1017 | (cdr (assoc "feedid" listinfo))))) |
1018 | feedinfo))) | |
91472578 | 1019 | (cdr (assoc |
229b59da G |
1020 | (gnus-completing-read |
1021 | "Multiple feeds found. Select one" | |
1022 | selection t) urllist))))))))) | |
23f87bed MB |
1023 | |
1024 | (defun nnrss-rss-p (data) | |
91472578 MB |
1025 | "Test if DATA is an RSS feed. |
1026 | Simply ensures that the first element is rss or rdf." | |
23f87bed MB |
1027 | (or (eq (caar data) 'rss) |
1028 | (eq (caar data) 'rdf:RDF))) | |
1029 | ||
1030 | (defun nnrss-rss-title-description (rss-namespace data url) | |
1031 | "Return the title of an RSS feed." | |
1032 | (if (nnrss-rss-p data) | |
1033 | (let ((description (intern (concat rss-namespace "description"))) | |
1034 | (title (intern (concat rss-namespace "title"))) | |
1035 | (channel (nnrss-find-el (intern (concat rss-namespace "channel")) | |
1036 | data))) | |
1037 | (list | |
1038 | (cons 'description (caddr (nth 0 (nnrss-find-el description channel)))) | |
1039 | (cons 'title (caddr (nth 0 (nnrss-find-el title channel)))) | |
1040 | (cons 'href url))))) | |
1041 | ||
1042 | (defun nnrss-get-namespace-prefix (el uri) | |
1043 | "Given EL (containing a parsed element) and URI (containing a string | |
1044 | that gives the URI for which you want to retrieve the namespace | |
1045 | prefix), return the prefix." | |
1046 | (let* ((prefix (car (rassoc uri (cadar el)))) | |
91472578 | 1047 | (nslist (if prefix |
23f87bed MB |
1048 | (split-string (symbol-name prefix) ":"))) |
1049 | (ns (cond ((eq (length nslist) 1) ; no prefix given | |
1050 | "") | |
1051 | ((eq (length nslist) 2) ; extract prefix | |
1052 | (cadr nslist))))) | |
91472578 | 1053 | (if (and ns (not (string= ns ""))) |
23f87bed MB |
1054 | (concat ns ":") |
1055 | ns))) | |
1056 | ||
1057 | (provide 'nnrss) | |
1058 | ||
23f87bed | 1059 | ;;; nnrss.el ends here |