Mercurial > emacs
annotate lisp/net/newsticker-backend.el @ 95895:2ab93a71b21a
(vc-delete-file): Bound default-directory before calling
the backend.
author | Dan Nicolaescu <dann@ics.uci.edu> |
---|---|
date | Fri, 13 Jun 2008 14:40:57 +0000 |
parents | 91e240b4d487 |
children |
rev | line source |
---|---|
95675 | 1 ;;; newsticker-backend.el --- Retrieval backend for newsticker. |
2 | |
95762
74bfdeb9cb31
Correct copyright years to reflect the original newsticker.el from
Glenn Morris <rgm@gnu.org>
parents:
95752
diff
changeset
|
3 ;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 |
74bfdeb9cb31
Correct copyright years to reflect the original newsticker.el from
Glenn Morris <rgm@gnu.org>
parents:
95752
diff
changeset
|
4 ;; Free Software Foundation, Inc. |
95675 | 5 |
6 ;; Author: Ulf Jasper <ulf.jasper@web.de> | |
7 ;; Filename: newsticker-backend.el | |
8 ;; URL: http://www.nongnu.org/newsticker | |
9 ;; Keywords: News, RSS, Atom | |
10 ;; Time-stamp: "8. Juni 2008, 17:18:04 (ulf)" | |
11 | |
12 ;; ====================================================================== | |
13 | |
95763 | 14 ;; This file is part of GNU Emacs. |
15 | |
95675 | 16 ;; GNU Emacs is free software: you can redistribute it and/or modify |
17 ;; it under the terms of the GNU General Public License as published by | |
18 ;; the Free Software Foundation, either version 3 of the License, or | |
19 ;; (at your option) any later version. | |
20 | |
21 ;; GNU Emacs is distributed in the hope that it will be useful, | |
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 ;; GNU General Public License for more details. | |
25 | |
26 ;; You should have received a copy of the GNU General Public License | |
27 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
28 | |
29 ;; ====================================================================== | |
30 | |
31 ;;; Commentary: | |
32 | |
33 ;; See newsticker.el | |
34 | |
35 ;; ====================================================================== | |
36 ;;; Code: | |
37 | |
38 (require 'derived) | |
39 (require 'xml) | |
40 | |
41 ;; Silence warnings | |
42 (defvar w3-mode-map) | |
43 (defvar w3m-minor-mode-map) | |
44 | |
45 | |
46 (defvar newsticker--retrieval-timer-list nil | |
47 "List of timers for news retrieval. | |
48 This is an alist, each element consisting of (feed-name . timer).") | |
49 | |
50 (defvar newsticker--download-logos nil | |
51 "If non-nil download feed logos if available.") | |
52 | |
53 (defvar newsticker--sentinel-callback nil | |
54 "Function called at end of `newsticker--sentinel'.") | |
55 | |
56 ;;;###autoload | |
57 (defun newsticker-running-p () | |
58 "Check whether newsticker is running. | |
59 Return t if newsticker is running, nil otherwise. Newsticker is | |
60 considered to be running if the newsticker timer list is not empty." | |
61 (> (length newsticker--retrieval-timer-list) 0)) | |
62 | |
63 ;; ====================================================================== | |
64 ;;; Customization | |
65 ;; ====================================================================== | |
66 (defgroup newsticker nil | |
67 "Aggregator for RSS and Atom feeds." | |
68 :group 'applications) | |
69 | |
70 (defconst newsticker--raw-url-list-defaults | |
71 '(("CNET News.com" | |
72 "http://export.cnet.com/export/feeds/news/rss/1,11176,,00.xml") | |
73 ("Debian Security Advisories" | |
74 "http://www.debian.org/security/dsa.en.rdf") | |
75 ("Debian Security Advisories - Long format" | |
76 "http://www.debian.org/security/dsa-long.en.rdf") | |
77 ("Emacs Wiki" | |
78 "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" | |
79 nil | |
80 3600) | |
81 ("Freshmeat.net" | |
82 "http://freshmeat.net/backend/fm.rdf") | |
83 ("Kuro5hin.org" | |
84 "http://www.kuro5hin.org/backend.rdf") | |
85 ("LWN (Linux Weekly News)" | |
86 "http://lwn.net/headlines/rss") | |
87 ("NewsForge" | |
88 "http://newsforge.com/index.rss") | |
89 ("NY Times: Technology" | |
90 "http://partners.userland.com/nytRss/technology.xml") | |
91 ("NY Times" | |
92 "http://partners.userland.com/nytRss/nytHomepage.xml") | |
93 ("Quote of the day" | |
94 "http://www.quotationspage.com/data/qotd.rss" | |
95 "07:00" | |
96 86400) | |
97 ("The Register" | |
98 "http://www.theregister.co.uk/tonys/slashdot.rdf") | |
99 ("slashdot" | |
100 "http://slashdot.org/index.rss" | |
101 nil | |
102 3600) ;/. will ban you if under 3600 seconds! | |
103 ("Wired News" | |
104 "http://www.wired.com/news_drop/netcenter/netcenter.rdf") | |
105 ("Heise News (german)" | |
106 "http://www.heise.de/newsticker/heise.rdf") | |
107 ("Tagesschau (german)" | |
108 "http://www.tagesschau.de/newsticker.rdf" | |
109 nil | |
110 1800) | |
111 ("Telepolis (german)" | |
112 "http://www.heise.de/tp/news.rdf")) | |
113 "Default URL list in raw form. | |
114 This list is fed into defcustom via `newsticker--splicer'.") | |
115 | |
116 (defun newsticker--splicer (item) | |
117 "Convert ITEM for splicing into `newsticker-url-list-defaults'." | |
118 (let ((result (list 'list :tag (nth 0 item) (list 'const (nth 0 item)))) | |
119 (element (cdr item))) | |
120 (while element | |
121 (setq result (append result (list (list 'const (car element))))) | |
122 (setq element (cdr element))) | |
123 result)) | |
124 | |
125 (defun newsticker--set-customvar-retrieval (symbol value) | |
126 "Set retrieval related newsticker-variable SYMBOL value to VALUE. | |
127 Calls all actions which are necessary in order to make the new | |
128 value effective." | |
129 (if (or (not (boundp symbol)) | |
130 (equal (symbol-value symbol) value)) | |
131 (set symbol value) | |
132 ;; something must have changed | |
133 (let ((need-restart nil) | |
134 (new-or-changed-feeds nil) | |
135 (removed-feeds)) | |
136 (cond ((eq symbol 'newsticker-retrieval-interval) | |
137 (setq need-restart t)) | |
138 ((memq symbol '(newsticker-url-list-defaults newsticker-url-list)) | |
139 (dolist (elt value) | |
140 (unless (member elt (symbol-value symbol)) | |
141 (setq new-or-changed-feeds (cons elt new-or-changed-feeds)))) | |
142 (dolist (elt (symbol-value symbol)) | |
143 (unless (member elt value) | |
144 (setq removed-feeds (cons elt removed-feeds)))))) | |
145 (cond (need-restart | |
146 (set symbol value) | |
147 (when (newsticker-running-p) | |
148 (message "Restarting newsticker") | |
149 (newsticker-stop) | |
150 (newsticker-start))) | |
151 (t | |
152 (dolist (feed removed-feeds) | |
153 (message "Stopping feed `%s'" (car feed)) | |
154 (newsticker--stop-feed (car feed))) | |
155 (dolist (feed new-or-changed-feeds) | |
156 (message "Starting feed `%s'" (car feed)) | |
157 (newsticker--stop-feed (car feed)) | |
158 (newsticker--start-feed feed)) | |
159 (unless new-or-changed-feeds | |
160 (when newsticker--sentinel-callback | |
161 (funcall newsticker--sentinel-callback))))) | |
162 (set symbol value)))) | |
163 | |
164 ;; ====================================================================== | |
165 ;; retrieval | |
166 (defgroup newsticker-retrieval nil | |
167 "Settings for news retrieval." | |
168 :group 'newsticker) | |
169 | |
170 (defcustom newsticker-url-list-defaults | |
171 '(("Emacs Wiki" | |
172 "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" | |
173 nil | |
174 3600)) | |
175 "A customizable list of news feeds to select from. | |
176 These were mostly extracted from the Radio Community Server at | |
177 http://subhonker6.userland.com/rcsPublic/rssHotlist. | |
178 | |
179 You may add other entries in `newsticker-url-list'." | |
180 :type `(set ,@(mapcar `newsticker--splicer | |
181 newsticker--raw-url-list-defaults)) | |
182 :set 'newsticker--set-customvar-retrieval | |
183 :group 'newsticker-retrieval) | |
184 | |
185 (defcustom newsticker-url-list nil | |
186 "The news feeds which you like to watch. | |
187 | |
188 This alist will be used in addition to selection made customizing | |
189 `newsticker-url-list-defaults'. | |
190 | |
191 This is an alist. Each element consists of two items: a LABEL and a URL, | |
192 optionally followed by a START-TIME, INTERVAL specifier and WGET-ARGUMENTS. | |
193 | |
194 The LABEL gives the name of the news feed. It can be an arbitrary string. | |
195 | |
196 The URL gives the location of the news feed. It must point to a valid | |
197 RSS or Atom file. The file is retrieved by calling wget, or whatever you | |
198 specify as `newsticker-wget-name'. | |
199 | |
200 URL may also be a function which returns news data. In this case | |
201 `newsticker-retrieval-method' etc. are ignored for this feed. | |
202 | |
203 The START-TIME can be either a string, or nil. If it is a string it | |
204 specifies a fixed time at which this feed shall be retrieved for the | |
205 first time. (Examples: \"11:00pm\", \"23:00\".) If it is nil (or | |
206 unspecified), this feed will be retrieved immediately after calling | |
207 `newsticker-start'. | |
208 | |
209 The INTERVAL specifies the time between retrievals for this feed. If it | |
210 is nil (or unspecified) the default interval value as set in | |
211 `newsticker-retrieval-interval' is used. | |
212 | |
213 \(newsticker.el calls `run-at-time'. The newsticker-parameters START-TIME | |
214 and INTERVAL correspond to the `run-at-time'-parameters TIME and REPEAT.) | |
215 | |
216 WGET-ARGUMENTS specifies arguments for wget (see `newsticker-wget-name') | |
217 which apply for this feed only, overriding the value of | |
218 `newsticker-wget-arguments'." | |
219 :type '(repeat (list :tag "News feed" | |
220 (string :tag "Label") | |
221 (choice :tag "URI" | |
222 (string :tag "String") | |
223 (function :tag "Function")) | |
224 (choice :tag "Start" | |
225 (const :tag "Default" nil) | |
226 (string :tag "Fixed Time")) | |
227 (choice :tag "Interval" | |
228 (const :tag "Default" nil) | |
229 (const :tag "Hourly" 3600) | |
230 (const :tag "Daily" 86400) | |
231 (const :tag "Weekly" 604800) | |
232 (integer :tag "Interval")) | |
233 (choice :tag "Wget Arguments" | |
234 (const :tag "Default arguments" nil) | |
235 (repeat :tag "Special arguments" string)))) | |
236 :set 'newsticker--set-customvar-retrieval | |
237 :group 'newsticker-retrieval) | |
238 | |
239 (defcustom newsticker-retrieval-method | |
240 'intern | |
241 "Method for retrieving news from the web, either `intern' or `extern'. | |
242 Default value `intern' uses Emacs' built-in asynchronous download | |
243 capabilities ('url-retrieve'). If set to `extern' the external | |
244 program wget is used, see `newsticker-wget-name'." | |
245 :type '(choice :tag "Method" | |
246 (const :tag "Intern" intern) | |
247 (const :tag "Extern" extern)) | |
248 :group 'newsticker-retrieval) | |
249 | |
250 (defcustom newsticker-wget-name | |
251 "wget" | |
252 "Name of the program which is called to retrieve news from the web. | |
253 The canonical choice is wget but you may take any other program which is | |
254 able to return the contents of a news feed file on stdout." | |
255 :type 'string | |
256 :group 'newsticker-retrieval) | |
257 | |
258 (defcustom newsticker-wget-arguments | |
259 '("-q" "-O" "-") | |
260 "Arguments which are passed to wget. | |
261 There is probably no reason to change the default settings, unless you | |
262 are living behind a firewall." | |
263 :type '(repeat (string :tag "Argument")) | |
264 :group 'newsticker-retrieval) | |
265 | |
266 (defcustom newsticker-retrieval-interval | |
267 3600 | |
268 "Time interval for retrieving new news items (seconds). | |
269 If this value is not positive (i.e. less than or equal to 0) | |
270 items are retrieved only once! | |
271 Please note that some feeds, e.g. Slashdot, will ban you if you | |
272 make it less than 1800 seconds (30 minutes)!" | |
273 :type '(choice :tag "Interval" | |
274 (const :tag "No automatic retrieval" 0) | |
275 (const :tag "Hourly" 3600) | |
276 (const :tag "Daily" 86400) | |
277 (const :tag "Weekly" 604800) | |
278 (integer :tag "Interval")) | |
279 :set 'newsticker--set-customvar-retrieval | |
280 :group 'newsticker-retrieval) | |
281 | |
282 (defcustom newsticker-desc-comp-max | |
283 100 | |
284 "Relevant length of headline descriptions. | |
285 This value gives the maximum number of characters which will be | |
286 taken into account when newsticker compares two headline | |
287 descriptions." | |
288 :type 'integer | |
289 :group 'newsticker-retrieval) | |
290 | |
291 ;; ====================================================================== | |
292 ;; headline processing | |
293 (defgroup newsticker-headline-processing nil | |
294 "Settings for the automatic processing of headlines." | |
295 :group 'newsticker) | |
296 | |
297 (defcustom newsticker-automatically-mark-items-as-old | |
298 t | |
299 "Decides whether to automatically mark items as old. | |
300 If t a new item is considered as new only after its first retrieval. As | |
301 soon as it is retrieved a second time, it becomes old. If not t all | |
302 items stay new until you mark them as old. This is done in the | |
303 *newsticker* buffer." | |
304 :type 'boolean | |
305 :group 'newsticker-headline-processing) | |
306 | |
307 (defcustom newsticker-automatically-mark-visited-items-as-old | |
308 t | |
309 "Decides whether to automatically mark visited items as old. | |
310 If t an item is marked as old as soon as the associated link is | |
311 visited, i.e. after pressing RET or mouse2 on the item's | |
312 headline." | |
313 | |
314 :type 'boolean | |
315 :group 'newsticker-headline-processing) | |
316 | |
317 (defcustom newsticker-keep-obsolete-items | |
318 t | |
319 "Decides whether to keep unread items which have been removed from feed. | |
320 If t a new item, which has been removed from the feed, is kept in | |
321 the cache until it is marked as read." | |
322 :type 'boolean | |
323 :group 'newsticker-headline-processing) | |
324 | |
325 (defcustom newsticker-obsolete-item-max-age | |
326 (* 60 60 24) | |
327 "Maximal age of obsolete items, in seconds. | |
328 Obsolete items which are older than this value will be silently | |
329 deleted at the next retrieval." | |
330 :type 'integer | |
331 :group 'newsticker-headline-processing) | |
332 | |
333 (defcustom newsticker-auto-mark-filter-list | |
334 nil | |
335 "A list of filters for automatically marking headlines. | |
336 | |
337 This is an alist of the form (FEED-NAME PATTERN-LIST). I.e. each | |
338 element consists of a FEED-NAME a PATTERN-LIST. Each element of | |
339 the pattern-list has the form (AGE TITLE-OR-DESCRIPTION REGEXP). | |
340 AGE must be one of the symbols 'old or 'immortal. | |
341 TITLE-OR-DESCRIPTION must be on of the symbols 'title, | |
342 'description, or 'all. REGEXP is a regular expression, i.e. a | |
343 string. | |
344 | |
345 This filter is checked after a new headline has been retrieved. | |
346 If FEED-NAME matches the name of the corresponding news feed, the | |
347 pattern-list is checked: The new headline will be marked as AGE | |
348 if REGEXP matches the headline's TITLE-OR-DESCRIPTION. | |
349 | |
350 If, for example, `newsticker-auto-mark-filter-list' looks like | |
351 \((slashdot ('old 'title \"^Forget me!$\") ('immortal 'title \"Read me\") | |
352 \('immortal 'all \"important\")))) | |
353 | |
354 then all articles from slashdot are marked as old if they have | |
355 the title \"Forget me!\". All articles with a title containing | |
356 the string \"Read me\" are marked as immortal. All articles which | |
357 contain the string \"important\" in their title or their | |
358 description are marked as immortal." | |
359 :type '(repeat (list :tag "Auto mark filter" | |
360 (string :tag "Feed name") | |
361 (repeat | |
362 (list :tag "Filter element" | |
363 (choice | |
364 :tag "Auto-assigned age" | |
365 (const :tag "Old" old) | |
366 (const :tag "Immortal" immortal)) | |
367 (choice | |
368 :tag "Title/Description" | |
369 (const :tag "Title" title) | |
370 (const :tag "Description" description) | |
371 (const :tag "All" all)) | |
372 (string :tag "Regexp"))))) | |
373 :group 'newsticker-headline-processing) | |
374 | |
375 ;; ====================================================================== | |
376 ;; hooks | |
377 (defgroup newsticker-hooks nil | |
378 "Settings for newsticker hooks." | |
379 :group 'newsticker) | |
380 | |
381 (defcustom newsticker-start-hook | |
382 nil | |
383 "Hook run when starting newsticker. | |
384 This hook is run at the very end of `newsticker-start'." | |
385 :options '(newsticker-start-ticker) | |
386 :type 'hook | |
387 :group 'newsticker-hooks) | |
388 | |
389 (defcustom newsticker-stop-hook | |
390 nil | |
391 "Hook run when stopping newsticker. | |
392 This hook is run at the very end of `newsticker-stop'." | |
393 :options nil | |
394 :type 'hook | |
395 :group 'newsticker-hooks) | |
396 | |
397 (defcustom newsticker-new-item-functions | |
398 nil | |
399 "List of functions run after a new headline has been retrieved. | |
400 Each function is called with the following three arguments: | |
401 FEED the name of the corresponding news feed, | |
402 TITLE the title of the headline, | |
403 DESC the decoded description of the headline. | |
404 | |
405 See `newsticker-download-images', and | |
406 `newsticker-download-enclosures' for sample functions. | |
407 | |
408 Please note that these functions are called only once for a | |
409 headline after it has been retrieved for the first time." | |
410 :type 'hook | |
411 :options '(newsticker-download-images | |
412 newsticker-download-enclosures) | |
413 :group 'newsticker-hooks) | |
414 | |
415 ;; ====================================================================== | |
416 ;; miscellaneous | |
417 (defgroup newsticker-miscellaneous nil | |
418 "Miscellaneous newsticker settings." | |
419 :group 'newsticker) | |
420 | |
421 (defcustom newsticker-cache-filename | |
422 "~/.newsticker-cache" | |
423 "Name of the newsticker cache file." | |
424 :type 'string | |
425 :group 'newsticker-miscellaneous) | |
426 | |
427 (defcustom newsticker-imagecache-dirname | |
428 "~/.newsticker-images" | |
429 "Name of the directory where newsticker stores cached images." | |
430 :type 'string | |
431 :group 'newsticker-miscellaneous) | |
432 | |
433 ;; debugging | |
434 (defcustom newsticker-debug | |
435 nil | |
436 "Enables some features needed for debugging newsticker.el. | |
437 | |
438 If set to t newsticker.el will print lots of debugging messages, and the | |
439 buffers *newsticker-wget-<feed>* will not be closed." | |
440 :type 'boolean | |
441 :group 'newsticker-miscellaneous) | |
442 | |
443 ;; ====================================================================== | |
444 ;;; Compatibility section, XEmacs, Emacs | |
445 ;; ====================================================================== | |
446 (unless (fboundp 'time-add) | |
447 (require 'time-date);;FIXME | |
448 (defun time-add (t1 t2) | |
449 (seconds-to-time (+ (time-to-seconds t1) (time-to-seconds t2))))) | |
450 | |
451 (unless (fboundp 'match-string-no-properties) | |
452 (defalias 'match-string-no-properties 'match-string)) | |
453 | |
454 (when (featurep 'xemacs) | |
455 (unless (fboundp 'replace-regexp-in-string) | |
456 (defun replace-regexp-in-string (re rp st) | |
457 (save-match-data ;; apparently XEmacs needs save-match-data | |
458 (replace-in-string st re rp))))) | |
459 | |
460 ;; copied from subr.el | |
461 (unless (fboundp 'add-to-invisibility-spec) | |
462 (defun add-to-invisibility-spec (arg) | |
463 "Add elements to `buffer-invisibility-spec'. | |
464 See documentation for `buffer-invisibility-spec' for the kind of elements | |
465 that can be added." | |
466 (if (eq buffer-invisibility-spec t) | |
467 (setq buffer-invisibility-spec (list t))) | |
468 (setq buffer-invisibility-spec | |
469 (cons arg buffer-invisibility-spec)))) | |
470 | |
471 ;; copied from subr.el | |
472 (unless (fboundp 'remove-from-invisibility-spec) | |
473 (defun remove-from-invisibility-spec (arg) | |
474 "Remove elements from `buffer-invisibility-spec'." | |
475 (if (consp buffer-invisibility-spec) | |
476 (setq buffer-invisibility-spec | |
477 (delete arg buffer-invisibility-spec))))) | |
478 | |
479 ;; ====================================================================== | |
480 ;;; Internal variables | |
481 ;; ====================================================================== | |
482 (defvar newsticker--item-list nil | |
483 "List of newsticker items.") | |
484 (defvar newsticker--item-position 0 | |
485 "Actual position in list of newsticker items.") | |
486 (defvar newsticker--prev-message "There was no previous message yet!" | |
487 "Last message that the newsticker displayed.") | |
488 (defvar newsticker--scrollable-text "" | |
489 "The text which is scrolled smoothly in the echo area.") | |
490 (defvar newsticker--buffer-uptodate-p nil | |
491 "Tells whether the newsticker buffer is up to date.") | |
492 (defvar newsticker--latest-update-time (current-time) | |
493 "The time at which the latest news arrived.") | |
494 (defvar newsticker--process-ids nil | |
495 "List of PIDs of active newsticker processes.") | |
496 | |
497 (defvar newsticker--cache nil "Cached newsticker data. | |
498 This is a list of the form | |
499 | |
500 ((label1 | |
501 (title description link time age index preformatted-contents | |
502 preformatted-title extra-elements) | |
503 ...) | |
504 (label2 | |
505 (title description link time age index preformatted-contents | |
506 preformatted-title extra-elements) | |
507 ...) | |
508 ...) | |
509 | |
510 where LABEL is a symbol. TITLE, DESCRIPTION, and LINK are | |
511 strings. TIME is a time value as returned by `current-time'. | |
512 AGE is a symbol: 'new, 'old, 'immortal, and 'obsolete denote | |
513 ordinary news items, whereas 'feed denotes an item which is not a | |
514 headline but describes the feed itself. INDEX denotes the | |
515 original position of the item -- used for restoring the original | |
516 order. PREFORMATTED-CONTENTS and PREFORMATTED-TITLE hold the | |
517 formatted contents of the item's description and title. This | |
518 speeds things up if HTML rendering is used, which is rather | |
519 slow. EXTRA-ELEMENTS is an alist containing additional elements.") | |
520 | |
521 (defvar newsticker--auto-narrow-to-feed nil | |
522 "Automatically narrow to current news feed. | |
523 If non-nil only the items of the current news feed are visible.") | |
524 | |
525 (defvar newsticker--auto-narrow-to-item nil | |
526 "Automatically narrow to current news item. | |
527 If non-nil only the current headline is visible.") | |
528 | |
529 (defconst newsticker--error-headline | |
530 "[COULD NOT DOWNLOAD HEADLINES!]" | |
531 "Title of error headline which will be inserted if news retrieval fails.") | |
532 | |
533 ;; ====================================================================== | |
534 ;;; Shortcuts | |
535 ;; ====================================================================== | |
536 (defsubst newsticker--title (item) | |
537 "Return title of ITEM." | |
538 (nth 0 item)) | |
539 (defsubst newsticker--desc (item) | |
540 "Return description of ITEM." | |
541 (nth 1 item)) | |
542 (defsubst newsticker--link (item) | |
543 "Return link of ITEM." | |
544 (nth 2 item)) | |
545 (defsubst newsticker--time (item) | |
546 "Return time of ITEM." | |
547 (nth 3 item)) | |
548 (defsubst newsticker--age (item) | |
549 "Return age of ITEM." | |
550 (nth 4 item)) | |
551 (defsubst newsticker--pos (item) | |
552 "Return position/index of ITEM." | |
553 (nth 5 item)) | |
554 (defsubst newsticker--preformatted-contents (item) | |
555 "Return pre-formatted text of ITEM." | |
556 (nth 6 item)) | |
557 (defsubst newsticker--preformatted-title (item) | |
558 "Return pre-formatted title of ITEM." | |
559 (nth 7 item)) | |
560 (defsubst newsticker--extra (item) | |
561 "Return extra attributes of ITEM." | |
562 (nth 8 item)) | |
563 (defsubst newsticker--guid-to-string (guid) | |
564 "Return string representation of GUID." | |
565 (if (stringp guid) | |
566 guid | |
567 (car (xml-node-children guid)))) | |
568 (defsubst newsticker--guid (item) | |
569 "Return guid of ITEM." | |
570 (newsticker--guid-to-string (assoc 'guid (newsticker--extra item)))) | |
571 (defsubst newsticker--enclosure (item) | |
572 "Return enclosure element of ITEM in the form \(...FIXME...\) or nil." | |
573 (let ((enclosure (assoc 'enclosure (newsticker--extra item)))) | |
574 (if enclosure | |
575 (xml-node-attributes enclosure)))) | |
576 (defun newsticker--real-feed-name (feed) | |
577 "Return real name of FEED." | |
578 (catch 'name | |
579 (mapc (lambda (item) | |
580 (if (eq (newsticker--age item) 'feed) | |
581 (throw 'name (newsticker--title item)))) | |
582 (cdr (newsticker--cache-get-feed feed))) | |
583 (symbol-name feed))) | |
584 | |
585 | |
586 ;; ====================================================================== | |
587 ;;; User fun | |
588 ;; ====================================================================== | |
589 | |
590 (defun newsticker--start-feed (feed &optional do-not-complain-if-running) | |
591 "Start retrieval timer for FEED. | |
592 If timer is running already a warning message is printed unless | |
593 DO-NOT-COMPLAIN-IF-RUNNING is not nil. Add the started | |
594 name/timer pair to `newsticker--retrieval-timer-list'." | |
595 (let* ((feed-name (car feed)) | |
596 (start-time (nth 2 feed)) | |
597 (interval (or (nth 3 feed) | |
598 newsticker-retrieval-interval)) | |
599 (timer (assoc (car feed) | |
600 newsticker--retrieval-timer-list))) | |
601 (if timer | |
602 (or do-not-complain-if-running | |
603 (message "Timer for %s is running already!" | |
604 feed-name)) | |
605 (newsticker--debug-msg "Starting timer for %s: %s, %d" | |
606 feed-name start-time interval) | |
607 ;; do not repeat retrieval if interval not positive | |
608 (if (<= interval 0) | |
609 (setq interval nil)) | |
610 ;; Suddenly XEmacs doesn't like start-time 0 | |
611 (if (or (not start-time) | |
612 (and (numberp start-time) (= start-time 0))) | |
613 (setq start-time 1)) | |
614 ;; (message "start-time %s" start-time) | |
615 (setq timer (run-at-time start-time interval | |
616 'newsticker-get-news feed-name)) | |
617 (if interval | |
618 (add-to-list 'newsticker--retrieval-timer-list | |
619 (cons feed-name timer)))))) | |
620 | |
621 ;;;###autoload | |
622 (defun newsticker-start (&optional do-not-complain-if-running) | |
623 "Start the newsticker. | |
624 Start the timers for display and retrieval. If the newsticker, i.e. the | |
625 timers, are running already a warning message is printed unless | |
626 DO-NOT-COMPLAIN-IF-RUNNING is not nil. | |
627 Run `newsticker-start-hook' if newsticker was not running already." | |
628 (interactive) | |
629 (let ((running (newsticker-running-p))) | |
630 ;; read old cache if it exists and newsticker is not running | |
631 (unless running | |
632 (let ((coding-system-for-read 'utf-8)) | |
633 (when (file-exists-p newsticker-cache-filename) | |
634 (with-temp-buffer | |
635 (insert-file-contents newsticker-cache-filename) | |
636 (goto-char (point-min)) | |
637 (condition-case nil | |
638 (setq newsticker--cache (read (current-buffer))) | |
639 (error | |
640 (message "Error while reading newsticker cache file!") | |
641 (setq newsticker--cache nil))))))) | |
642 ;; start retrieval timers -- one timer for each feed | |
643 (dolist (feed (append newsticker-url-list-defaults newsticker-url-list)) | |
644 (newsticker--start-feed feed)) | |
645 (unless running | |
646 (run-hooks 'newsticker-start-hook) | |
647 (message "Newsticker started!")))) | |
648 | |
649 (defun newsticker--stop-feed (feed-name) | |
650 "Stop retrieval for feed FEED-NAME. | |
651 Delete the stopped name/timer pair from `newsticker--retrieval-timer-list'." | |
652 (let ((name-and-timer (assoc feed-name newsticker--retrieval-timer-list))) | |
653 (when name-and-timer | |
654 (cancel-timer (cdr name-and-timer)) | |
655 (setq newsticker--retrieval-timer-list | |
656 (delete name-and-timer newsticker--retrieval-timer-list))))) | |
657 | |
658 (defun newsticker-stop () | |
659 "Stop the newsticker and the newsticker-ticker. | |
660 Cancel the timers for display and retrieval. Run `newsticker-stop-hook' | |
661 if newsticker has been running." | |
662 (interactive) | |
663 (newsticker--cache-update t) | |
664 (when (fboundp 'newsticker-stop-ticker) ; silence compiler warnings | |
665 (newsticker-stop-ticker)) | |
666 (when (newsticker-running-p) | |
667 (mapc (lambda (name-and-timer) | |
668 (newsticker--stop-feed (car name-and-timer))) | |
669 newsticker--retrieval-timer-list) | |
670 (setq newsticker--retrieval-timer-list nil) | |
671 (run-hooks 'newsticker-stop-hook) | |
672 (message "Newsticker stopped!"))) | |
673 | |
674 (defun newsticker-get-all-news () | |
675 "Launch retrieval of news from all configured newsticker sites. | |
676 This does NOT start the retrieval timers." | |
677 (interactive) | |
678 ;; launch retrieval of news | |
679 (mapc (lambda (item) | |
680 (newsticker-get-news (car item))) | |
681 (append newsticker-url-list-defaults newsticker-url-list))) | |
682 | |
683 (defun newsticker-save-item (feed item) | |
684 "Save FEED ITEM." | |
685 (interactive) | |
686 (let ((filename (read-string "Filename: " | |
687 (concat feed ":_" | |
688 (replace-regexp-in-string | |
689 " " "_" (newsticker--title item)) | |
690 ".html")))) | |
691 (with-temp-buffer | |
692 (insert (newsticker--desc item)) | |
693 (write-file filename t)))) | |
694 | |
695 (defun newsticker-add-url (url name) | |
696 "Add given URL under given NAME to `newsticker-url-list'. | |
697 If URL is nil it is searched at point." | |
698 (interactive | |
699 (list | |
700 (read-string "URL: " | |
701 (save-excursion | |
702 (end-of-line) | |
703 (and | |
704 (re-search-backward | |
705 "http://" | |
706 (if (> (point) (+ (point-min) 100)) | |
707 (- (point) 100) | |
708 (point-min)) | |
709 t) | |
710 (re-search-forward | |
711 "http://[-a-zA-Z0-9&/_.]*" | |
712 (if (< (point) (- (point-max) 200)) | |
713 (+ (point) 200) | |
714 (point-max)) | |
715 t) | |
716 (buffer-substring-no-properties (match-beginning 0) | |
717 (match-end 0))))) | |
718 (read-string "Name: "))) | |
719 (add-to-list 'newsticker-url-list (list name url nil nil nil) t) | |
720 (customize-variable 'newsticker-url-list)) | |
721 | |
722 (defun newsticker-customize () | |
723 "Open the newsticker customization group." | |
724 (interactive) | |
725 (customize-group "newsticker")) | |
726 | |
727 ;; ====================================================================== | |
728 ;;; Local stuff | |
729 ;; ====================================================================== | |
730 (defun newsticker--get-news-by-funcall (feed-name function) | |
731 "Get news for the site FEED-NAME by calling FUNCTION. | |
732 See `newsticker-get-news'." | |
733 (let ((buffername (concat " *newsticker-funcall-" feed-name "*"))) | |
734 (save-excursion | |
735 (set-buffer (get-buffer-create buffername)) | |
736 (erase-buffer) | |
737 (insert (string-to-multibyte (funcall function feed-name))) | |
738 (newsticker--sentinel-work nil t feed-name function | |
739 (current-buffer))))) | |
740 | |
741 (defun newsticker--get-news-by-url (feed-name url) | |
742 "Get news for the site FEED-NAME from address URL using `url-retrieve'. | |
743 See `newsticker-get-news'." | |
744 (let ((coding-system-for-read 'no-conversion)) | |
745 (url-retrieve url 'newsticker--get-news-by-url-callback (list feed-name))) | |
746 (force-mode-line-update)) | |
747 | |
748 (defun newsticker--get-news-by-url-callback (status feed-name) | |
749 "Callback function for `newsticker--get-news-by-url'. | |
750 STATUS is the return status as delivered by `url-retrieve', and | |
751 FEED-NAME is the name of the feed that the news were retrieved | |
752 from." | |
753 (let ((buf (get-buffer-create (concat " *newsticker-url-" feed-name "*"))) | |
754 (result (string-to-multibyte (buffer-string)))) | |
755 (set-buffer buf) | |
756 (erase-buffer) | |
757 (insert result) | |
758 ;; remove MIME header | |
759 (goto-char (point-min)) | |
760 (search-forward "\n\n") | |
761 (delete-region (point-min) (point)) | |
762 ;; read the rss/atom contents | |
763 (newsticker--sentinel-work nil t feed-name "url-retrieve" (current-buffer)) | |
764 (when status | |
765 (let ((status-type (car status)) | |
766 (status-details (cdr status))) | |
767 (cond ((eq status-type :redirect) | |
768 ;; don't care about redirects | |
769 ) | |
770 ((eq status-type :error) | |
771 (message "%s: Error while retrieving news from %s: %s: \"%s\"" | |
772 (format-time-string "%A, %H:%M" (current-time)) | |
773 feed-name | |
774 (car status-details) (cdr status-details)))))))) | |
775 | |
776 (defun newsticker--get-news-by-wget (feed-name url wget-arguments) | |
777 "Get news for the site FEED-NAME from address URL using wget. | |
778 WGET-ARGUMENTS is a list of arguments for wget. | |
779 See `newsticker-get-news'." | |
780 (let ((buffername (concat " *newsticker-wget-" feed-name "*"))) | |
781 (save-excursion | |
782 (set-buffer (get-buffer-create buffername)) | |
783 (erase-buffer) | |
784 ;; throw an error if there is an old wget-process around | |
785 (if (get-process feed-name) | |
786 (error "Another wget-process is running for %s" feed-name)) | |
787 ;; start wget | |
788 (let* ((args (append wget-arguments (list url))) | |
789 (proc (apply 'start-process feed-name buffername | |
790 newsticker-wget-name args))) | |
791 (set-process-coding-system proc 'no-conversion 'no-conversion) | |
792 (set-process-sentinel proc 'newsticker--sentinel) | |
793 (setq newsticker--process-ids (cons (process-id proc) | |
794 newsticker--process-ids)) | |
795 (force-mode-line-update))))) | |
796 | |
797 (defun newsticker-get-news (feed-name) | |
798 "Get news from the site FEED-NAME and load feed logo. | |
799 FEED-NAME must be a string which occurs as the label (i.e. the first element) | |
800 in an element of `newsticker-url-list' or `newsticker-url-list-defaults'." | |
801 (newsticker--debug-msg "%s: Getting news for %s" | |
802 (format-time-string "%A, %H:%M" (current-time)) | |
803 feed-name) | |
804 (let* ((item (or (assoc feed-name newsticker-url-list) | |
805 (assoc feed-name newsticker-url-list-defaults) | |
806 (error | |
807 "Cannot get news for %s: Check newsticker-url-list" | |
808 feed-name))) | |
809 (url (cadr item)) | |
810 (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) | |
811 newsticker-wget-arguments))) | |
812 (if (functionp url) | |
813 (newsticker--get-news-by-funcall feed-name url) | |
814 (if (eq newsticker-retrieval-method 'intern) | |
815 (newsticker--get-news-by-url feed-name url) | |
816 (newsticker--get-news-by-wget feed-name url wget-arguments))))) | |
817 | |
818 ;; ====================================================================== | |
819 ;; Parsing | |
820 ;; ====================================================================== | |
821 | |
822 (defun newsticker--sentinel (process event) | |
823 "Sentinel for extracting news titles from an RDF buffer. | |
824 Argument PROCESS is the process which has just changed its state. | |
825 Argument EVENT tells what has happened to the process." | |
826 (let ((p-status (process-status process)) | |
827 (exit-status (process-exit-status process)) | |
828 (name (process-name process)) | |
829 (command (process-command process)) | |
830 (buffer (process-buffer process))) | |
831 (newsticker--sentinel-work event | |
832 (and (eq p-status 'exit) | |
833 (= exit-status 0)) | |
834 name command buffer))) | |
835 | |
836 (defun newsticker--sentinel-work (event status-ok name command buffer) | |
837 "Actually do the sentinel work. | |
838 Argument EVENT tells what has happened to the retrieval process. | |
839 Argument STATUS-OK is the final status of the retrieval process, | |
840 non-nil meaning retrieval was successful. | |
841 Argument NAME is the name of the retrieval process. | |
842 Argument COMMAND is the command of the retrieval process. | |
843 Argument BUFFER is the buffer of the retrieval process." | |
844 (let ((time (current-time)) | |
845 (name-symbol (intern name)) | |
846 (something-was-added nil)) | |
847 ;; catch known errors (zombie processes, rubbish-xml etc. | |
848 ;; if an error occurs the news feed is not updated! | |
849 (catch 'oops | |
850 (unless status-ok | |
851 (setq newsticker--cache | |
852 (newsticker--cache-add | |
853 newsticker--cache | |
854 name-symbol | |
855 newsticker--error-headline | |
856 (format | |
857 (concat "%s: Newsticker could not retrieve news from %s.\n" | |
858 "Return status: `%s'\n" | |
859 "Command was `%s'") | |
860 (format-time-string "%A, %H:%M" (current-time)) | |
861 name event command) | |
862 "" | |
863 (current-time) | |
864 'new | |
865 0 nil)) | |
866 (message "%s: Error while retrieving news from %s" | |
867 (format-time-string "%A, %H:%M" (current-time)) | |
868 name) | |
869 (throw 'oops nil)) | |
870 (let* ((coding-system 'utf-8) | |
871 (node-list | |
872 (save-current-buffer | |
873 (set-buffer buffer) | |
874 ;; a very very dirty workaround to overcome the | |
875 ;; problems with the newest (20030621) xml.el: | |
876 ;; remove all unnecessary whitespace | |
877 (goto-char (point-min)) | |
878 (while (re-search-forward ">[ \t\r\n]+<" nil t) | |
879 (replace-match "><" nil t)) | |
880 ;; and another brutal workaround (20031105)! For some | |
881 ;; reason the xml parser does not like the colon in the | |
882 ;; doctype name "rdf:RDF" | |
883 (goto-char (point-min)) | |
884 (if (re-search-forward "<!DOCTYPE[ \t\n]+rdf:RDF" nil t) | |
885 (replace-match "<!DOCTYPE rdfColonRDF" nil t)) | |
886 ;; finally.... ~##^°!!!!! | |
887 (goto-char (point-min)) | |
888 (while (search-forward "\r\n" nil t) | |
889 (replace-match "\n" nil t)) | |
890 ;; still more brutal workarounds (20040309)! The xml | |
891 ;; parser does not like doctype rss | |
892 (goto-char (point-min)) | |
893 (if (re-search-forward "<!DOCTYPE[ \t\n]+rss[ \t\n]*>" nil t) | |
894 (replace-match "" nil t)) | |
895 ;; And another one (20050618)! (Fixed in GNU Emacs 22.0.50.18) | |
896 ;; Remove comments to avoid this xml-parsing bug: | |
897 ;; "XML files can have only one toplevel tag" | |
898 (goto-char (point-min)) | |
899 (while (search-forward "<!--" nil t) | |
900 (let ((start (match-beginning 0))) | |
901 (unless (search-forward "-->" nil t) | |
902 (error "Can't find end of comment")) | |
903 (delete-region start (point)))) | |
904 ;; And another one (20050702)! If description is HTML | |
905 ;; encoded and starts with a `<', wrap the whole | |
906 ;; description in a CDATA expression. This happened for | |
907 ;; http://www.thefreedictionary.com/_/WoD/rss.aspx?type=quote | |
908 (goto-char (point-min)) | |
909 (while (re-search-forward | |
910 "<description>\\(<img.*?\\)</description>" nil t) | |
911 (replace-match | |
912 "<description><![CDATA[ \\1 ]]></description>")) | |
913 ;; And another one (20051123)! XML parser does not | |
914 ;; like this: <yweather:location city="Frankfurt/Main" | |
915 ;; region="" country="GM" /> | |
916 ;; try to "fix" empty attributes | |
917 ;; This happened for | |
918 ;; http://xml.weather.yahoo.com/forecastrss?p=GMXX0040&u=f | |
919 (goto-char (point-min)) | |
920 (while (re-search-forward "\\(<[^>]*\\)=\"\"" nil t) | |
921 (replace-match "\\1=\" \"")) | |
922 ;; | |
923 (set-buffer-modified-p nil) | |
924 ;; check coding system | |
925 (goto-char (point-min)) | |
926 (if (re-search-forward "encoding=\"\\([^\"]+\\)\"" | |
927 nil t) | |
928 (setq coding-system (intern (downcase (match-string 1)))) | |
929 (setq coding-system | |
930 (condition-case nil | |
931 (check-coding-system coding-system) | |
932 (coding-system-error | |
933 (message | |
934 "newsticker.el: ignoring coding system %s for %s" | |
935 coding-system name) | |
936 nil)))) | |
937 ;; Decode if possible | |
938 (when coding-system | |
939 (decode-coding-region (point-min) (point-max) | |
940 coding-system)) | |
941 (condition-case errordata | |
942 ;; The xml parser might fail | |
943 ;; or the xml might be bugged | |
944 (xml-parse-region (point-min) (point-max)) | |
945 (error (message "Could not parse %s: %s" | |
946 (buffer-name) (cadr errordata)) | |
947 (throw 'oops nil))))) | |
948 (topnode (car node-list)) | |
949 (channelnode (car (xml-get-children topnode 'channel))) | |
950 (imageurl nil)) | |
951 ;; mark all items as obsolete | |
952 (newsticker--cache-replace-age newsticker--cache | |
953 name-symbol | |
954 'new 'obsolete-new) | |
955 (newsticker--cache-replace-age newsticker--cache | |
956 name-symbol | |
957 'old 'obsolete-old) | |
958 (newsticker--cache-replace-age newsticker--cache | |
959 name-symbol | |
960 'feed 'obsolete-old) | |
961 | |
962 ;; check Atom/RSS version and call corresponding parser | |
963 (condition-case error-data | |
964 (if (cond | |
965 ;; RSS 0.91 | |
966 ((and (eq 'rss (xml-node-name topnode)) | |
967 (string= "0.91" (xml-get-attribute topnode 'version))) | |
968 (setq imageurl (newsticker--get-logo-url-rss-0.91 topnode)) | |
969 (newsticker--parse-rss-0.91 name time topnode)) | |
970 ;; RSS 0.92 | |
971 ((and (eq 'rss (xml-node-name topnode)) | |
972 (string= "0.92" (xml-get-attribute topnode 'version))) | |
973 (setq imageurl (newsticker--get-logo-url-rss-0.92 topnode)) | |
974 (newsticker--parse-rss-0.92 name time topnode)) | |
975 ;; RSS 1.0 | |
976 ((eq 'rdf:RDF (xml-node-name topnode)) | |
977 (setq imageurl (newsticker--get-logo-url-rss-1.0 topnode)) | |
978 (newsticker--parse-rss-1.0 name time topnode)) | |
979 ;; RSS 2.0 | |
980 ((and (eq 'rss (xml-node-name topnode)) | |
981 (string= "2.0" (xml-get-attribute topnode 'version))) | |
982 (setq imageurl (newsticker--get-logo-url-rss-2.0 topnode)) | |
983 (newsticker--parse-rss-2.0 name time topnode)) | |
984 ;; Atom 0.3 | |
985 ((and (eq 'feed (xml-node-name topnode)) | |
986 (string= "http://purl.org/atom/ns#" | |
987 (xml-get-attribute topnode 'xmlns))) | |
988 (setq imageurl (newsticker--get-logo-url-atom-0.3 topnode)) | |
989 (newsticker--parse-atom-0.3 name time topnode)) | |
990 ;; Atom 1.0 | |
991 ((and (eq 'feed (xml-node-name topnode)) | |
992 (string= "http://www.w3.org/2005/Atom" | |
993 (xml-get-attribute topnode 'xmlns))) | |
994 (setq imageurl (newsticker--get-logo-url-atom-1.0 topnode)) | |
995 (newsticker--parse-atom-1.0 name time topnode)) | |
996 ;; unknown feed type | |
997 (t | |
998 (newsticker--debug-msg "Feed type unknown: %s: %s" | |
999 (xml-node-name topnode) name) | |
1000 nil)) | |
1001 (setq something-was-added t)) | |
1002 (xerror (message "sentinelerror in %s: %s" name error-data))) | |
1003 | |
1004 ;; Remove those old items from cache which have been removed from | |
1005 ;; the feed | |
1006 (newsticker--cache-replace-age newsticker--cache | |
1007 name-symbol 'obsolete-old 'deleteme) | |
1008 (newsticker--cache-remove newsticker--cache name-symbol | |
1009 'deleteme) | |
1010 ;; Remove those new items from cache which have been removed from | |
1011 ;; the feed. Or keep them as `obsolete' | |
1012 (if (not newsticker-keep-obsolete-items) | |
1013 (newsticker--cache-remove newsticker--cache | |
1014 name-symbol 'obsolete-new) | |
1015 (setq newsticker--cache | |
1016 (newsticker--cache-mark-expired | |
1017 newsticker--cache name-symbol 'obsolete 'obsolete-expired | |
1018 newsticker-obsolete-item-max-age)) | |
1019 (newsticker--cache-remove newsticker--cache | |
1020 name-symbol 'obsolete-expired) | |
1021 (newsticker--cache-replace-age newsticker--cache | |
1022 name-symbol 'obsolete-new | |
1023 'obsolete)) | |
1024 (newsticker--update-process-ids) | |
1025 ;; setup scrollable text | |
1026 (when (= 0 (length newsticker--process-ids)) | |
1027 (when (fboundp 'newsticker--ticker-text-setup) ;silence | |
1028 ;compiler | |
1029 ;warnings | |
1030 (newsticker--ticker-text-setup))) | |
1031 (setq newsticker--latest-update-time (current-time)) | |
1032 (when something-was-added | |
1033 ;; FIXME: should we care about removed items as well? | |
1034 (newsticker--cache-update) | |
1035 (when (fboundp 'newsticker--buffer-set-uptodate) ;silence | |
1036 ;compiler | |
1037 ;warnings | |
1038 (newsticker--buffer-set-uptodate nil))) | |
1039 ;; kill the process buffer if wanted | |
1040 (unless newsticker-debug | |
1041 (kill-buffer buffer)) | |
1042 ;; launch retrieval of image | |
1043 (when (and imageurl newsticker--download-logos) | |
1044 (newsticker--image-get name imageurl))))) | |
1045 (when newsticker--sentinel-callback | |
1046 (funcall newsticker--sentinel-callback))) | |
1047 | |
1048 (defun newsticker--get-logo-url-atom-1.0 (node) | |
1049 "Return logo URL from atom 1.0 data in NODE." | |
1050 (car (xml-node-children | |
1051 (car (xml-get-children node 'logo))))) | |
1052 | |
1053 (defun newsticker--get-logo-url-atom-0.3 (node) | |
1054 "Return logo URL from atom 0.3 data in NODE." | |
1055 (car (xml-node-children | |
1056 (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | |
1057 | |
1058 (defun newsticker--get-logo-url-rss-2.0 (node) | |
1059 "Return logo URL from RSS 2.0 data in NODE." | |
1060 (car (xml-node-children | |
1061 (car (xml-get-children | |
1062 (car (xml-get-children | |
1063 (car (xml-get-children node 'channel)) 'image)) 'url))))) | |
1064 | |
1065 (defun newsticker--get-logo-url-rss-1.0 (node) | |
1066 "Return logo URL from RSS 1.0 data in NODE." | |
1067 (car (xml-node-children | |
1068 (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | |
1069 | |
1070 (defun newsticker--get-logo-url-rss-0.92 (node) | |
1071 "Return logo URL from RSS 0.92 data in NODE." | |
1072 (car (xml-node-children | |
1073 (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | |
1074 | |
1075 (defun newsticker--get-logo-url-rss-0.91 (node) | |
1076 "Return logo URL from RSS 0.91 data in NODE." | |
1077 (car (xml-node-children | |
1078 (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | |
1079 | |
1080 (defun newsticker--parse-atom-0.3 (name time topnode) | |
1081 "Parse Atom 0.3 data. | |
1082 Return value as well as arguments NAME, TIME, and TOPNODE are the | |
1083 same as in `newsticker--parse-atom-1.0'." | |
1084 (newsticker--debug-msg "Parsing Atom 0.3 feed %s" name) | |
1085 (let (new-feed new-item) | |
1086 (setq new-feed (newsticker--parse-generic-feed | |
1087 name time | |
1088 ;; title | |
1089 (car (xml-node-children | |
1090 (car (xml-get-children topnode 'title)))) | |
1091 ;; desc | |
1092 (car (xml-node-children | |
1093 (car (xml-get-children topnode 'content)))) | |
1094 ;; link | |
1095 (xml-get-attribute | |
1096 (car (xml-get-children topnode 'link)) 'href) | |
1097 ;; extra-elements | |
1098 (xml-node-children topnode))) | |
1099 (setq new-item (newsticker--parse-generic-items | |
1100 name time (xml-get-children topnode 'entry) | |
1101 ;; title-fn | |
1102 (lambda (node) | |
1103 (car (xml-node-children | |
1104 (car (xml-get-children node 'title))))) | |
1105 ;; desc-fn | |
1106 (lambda (node) | |
1107 (or (car (xml-node-children | |
1108 (car (xml-get-children node 'content)))) | |
1109 (car (xml-node-children | |
1110 (car (xml-get-children node 'summary)))))) | |
1111 ;; link-fn | |
1112 (lambda (node) | |
1113 (xml-get-attribute | |
1114 (car (xml-get-children node 'link)) 'href)) | |
1115 ;; time-fn | |
1116 (lambda (node) | |
1117 (newsticker--decode-rfc822-date | |
1118 (car (xml-node-children | |
1119 (car (xml-get-children node 'modified)))))) | |
1120 ;; guid-fn | |
1121 (lambda (node) | |
1122 (newsticker--guid-to-string | |
1123 (assoc 'guid (xml-node-children node)))) | |
1124 ;; extra-fn | |
1125 (lambda (node) | |
1126 (xml-node-children node)))) | |
1127 (or new-item new-feed))) | |
1128 | |
1129 (defun newsticker--parse-atom-1.0 (name time topnode) | |
1130 "Parse Atom 1.0 data. | |
1131 Argument NAME gives the name of a news feed. TIME gives the | |
1132 system time at which the data have been retrieved. TOPNODE | |
1133 contains the feed data as returned by the xml parser. | |
1134 | |
1135 For the Atom 1.0 specification see | |
1136 http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html" | |
1137 (newsticker--debug-msg "Parsing Atom 1.0 feed %s" name) | |
1138 (let (new-feed new-item) | |
1139 (setq new-feed (newsticker--parse-generic-feed | |
1140 name time | |
1141 ;; title | |
1142 (car (xml-node-children | |
1143 (car (xml-get-children topnode 'title)))) | |
1144 ;; desc | |
1145 (car (xml-node-children | |
1146 (car (xml-get-children topnode 'subtitle)))) | |
1147 ;; link | |
1148 (lambda (node) | |
1149 (xml-get-attribute | |
1150 (car (xml-get-children node 'link)) 'href)) | |
1151 ;; extra-elements | |
1152 (xml-node-children topnode))) | |
1153 (setq new-item (newsticker--parse-generic-items | |
1154 name time (xml-get-children topnode 'entry) | |
1155 ;; title-fn | |
1156 (lambda (node) | |
1157 (car (xml-node-children | |
1158 (car (xml-get-children node 'title))))) | |
1159 ;; desc-fn | |
1160 (lambda (node) | |
1161 (or (car (xml-node-children | |
1162 (car (xml-get-children node 'content)))) | |
1163 (car (xml-node-children | |
1164 (car (xml-get-children node 'summary)))))) | |
1165 ;; link-fn | |
1166 (lambda (node) | |
1167 (xml-get-attribute | |
1168 (car (xml-get-children node 'link)) 'href)) | |
1169 ;; time-fn | |
1170 (lambda (node) | |
1171 (newsticker--decode-iso8601-date | |
1172 (or (car (xml-node-children | |
1173 (car (xml-get-children node 'updated)))) | |
1174 (car (xml-node-children | |
1175 (car (xml-get-children node 'published))))))) | |
1176 ;; guid-fn | |
1177 (lambda (node) | |
1178 (car (xml-node-children | |
1179 (car (xml-get-children node 'id))))) | |
1180 ;; extra-fn | |
1181 (lambda (node) | |
1182 (xml-node-children node)))) | |
1183 (or new-item new-feed))) | |
1184 | |
1185 (defun newsticker--parse-rss-0.91 (name time topnode) | |
1186 "Parse RSS 0.91 data. | |
1187 Return value as well as arguments NAME, TIME, and TOPNODE are the | |
1188 same as in `newsticker--parse-atom-1.0'. | |
1189 | |
1190 For the RSS 0.91 specification see http://backend.userland.com/rss091 or | |
1191 http://my.netscape.com/publish/formats/rss-spec-0.91.html." | |
1192 (newsticker--debug-msg "Parsing RSS 0.91 feed %s" name) | |
1193 (let* ((channelnode (car (xml-get-children topnode 'channel))) | |
1194 (pub-date (newsticker--decode-rfc822-date | |
1195 (car (xml-node-children | |
1196 (car (xml-get-children channelnode 'pubDate)))))) | |
1197 is-new-feed has-new-items) | |
1198 (setq is-new-feed (newsticker--parse-generic-feed | |
1199 name time | |
1200 ;; title | |
1201 (car (xml-node-children | |
1202 (car (xml-get-children channelnode 'title)))) | |
1203 ;; desc | |
1204 (car (xml-node-children | |
1205 (car (xml-get-children channelnode | |
1206 'description)))) | |
1207 ;; link | |
1208 (car (xml-node-children | |
1209 (car (xml-get-children channelnode 'link)))) | |
1210 ;; extra-elements | |
1211 (xml-node-children channelnode))) | |
1212 (setq has-new-items (newsticker--parse-generic-items | |
1213 name time (xml-get-children channelnode 'item) | |
1214 ;; title-fn | |
1215 (lambda (node) | |
1216 (car (xml-node-children | |
1217 (car (xml-get-children node 'title))))) | |
1218 ;; desc-fn | |
1219 (lambda (node) | |
1220 (car (xml-node-children | |
1221 (car (xml-get-children node 'description))))) | |
1222 ;; link-fn | |
1223 (lambda (node) | |
1224 (car (xml-node-children | |
1225 (car (xml-get-children node 'link))))) | |
1226 ;; time-fn | |
1227 (lambda (node) | |
1228 (newsticker--decode-rfc822-date | |
1229 (car (xml-node-children | |
1230 (car (xml-get-children node 'pubDate)))))) | |
1231 ;; guid-fn | |
1232 (lambda (node) | |
1233 nil) | |
1234 ;; extra-fn | |
1235 (lambda (node) | |
1236 (xml-node-children node)))) | |
1237 (or has-new-items is-new-feed))) | |
1238 | |
1239 (defun newsticker--parse-rss-0.92 (name time topnode) | |
1240 "Parse RSS 0.92 data. | |
1241 Return value as well as arguments NAME, TIME, and TOPNODE are the | |
1242 same as in `newsticker--parse-atom-1.0'. | |
1243 | |
1244 For the RSS 0.92 specification see http://backend.userland.com/rss092." | |
1245 (newsticker--debug-msg "Parsing RSS 0.92 feed %s" name) | |
1246 (let* ((channelnode (car (xml-get-children topnode 'channel))) | |
1247 (pub-date (newsticker--decode-rfc822-date | |
1248 (car (xml-node-children | |
1249 (car (xml-get-children channelnode 'pubDate)))))) | |
1250 is-new-feed has-new-items) | |
1251 (setq is-new-feed (newsticker--parse-generic-feed | |
1252 name time | |
1253 ;; title | |
1254 (car (xml-node-children | |
1255 (car (xml-get-children channelnode 'title)))) | |
1256 ;; desc | |
1257 (car (xml-node-children | |
1258 (car (xml-get-children channelnode | |
1259 'description)))) | |
1260 ;; link | |
1261 (car (xml-node-children | |
1262 (car (xml-get-children channelnode 'link)))) | |
1263 ;; extra-elements | |
1264 (xml-node-children channelnode))) | |
1265 (setq has-new-items (newsticker--parse-generic-items | |
1266 name time (xml-get-children channelnode 'item) | |
1267 ;; title-fn | |
1268 (lambda (node) | |
1269 (car (xml-node-children | |
1270 (car (xml-get-children node 'title))))) | |
1271 ;; desc-fn | |
1272 (lambda (node) | |
1273 (car (xml-node-children | |
1274 (car (xml-get-children node 'description))))) | |
1275 ;; link-fn | |
1276 (lambda (node) | |
1277 (car (xml-node-children | |
1278 (car (xml-get-children node 'link))))) | |
1279 ;; time-fn | |
1280 (lambda (node) | |
1281 (newsticker--decode-rfc822-date | |
1282 (car (xml-node-children | |
1283 (car (xml-get-children node 'pubDate)))))) | |
1284 ;; guid-fn | |
1285 (lambda (node) | |
1286 nil) | |
1287 ;; extra-fn | |
1288 (lambda (node) | |
1289 (xml-node-children node)))) | |
1290 (or has-new-items is-new-feed))) | |
1291 | |
1292 (defun newsticker--parse-rss-1.0 (name time topnode) | |
1293 "Parse RSS 1.0 data. | |
1294 Return value as well as arguments NAME, TIME, and TOPNODE are the | |
1295 same as in `newsticker--parse-atom-1.0'. | |
1296 | |
1297 For the RSS 1.0 specification see http://web.resource.org/rss/1.0/spec." | |
1298 (newsticker--debug-msg "Parsing RSS 1.0 feed %s" name) | |
1299 (let* ((channelnode (car (xml-get-children topnode 'channel))) | |
1300 is-new-feed has-new-items) | |
1301 (setq is-new-feed (newsticker--parse-generic-feed | |
1302 name time | |
1303 ;; title | |
1304 (car (xml-node-children | |
1305 (car (xml-get-children channelnode 'title)))) | |
1306 ;; desc | |
1307 (car (xml-node-children | |
1308 (car (xml-get-children channelnode | |
1309 'description)))) | |
1310 ;; link | |
1311 (car (xml-node-children | |
1312 (car (xml-get-children channelnode 'link)))) | |
1313 ;; extra-elements | |
1314 (xml-node-children channelnode))) | |
1315 (setq has-new-items (newsticker--parse-generic-items | |
1316 name time (xml-get-children topnode 'item) | |
1317 ;; title-fn | |
1318 (lambda (node) | |
1319 (car (xml-node-children | |
1320 (car (xml-get-children node 'title))))) | |
1321 ;; desc-fn | |
1322 (lambda (node) | |
1323 (car (xml-node-children | |
1324 (car (xml-get-children node | |
1325 'description))))) | |
1326 ;; link-fn | |
1327 (lambda (node) | |
1328 (car (xml-node-children | |
1329 (car (xml-get-children node 'link))))) | |
1330 ;; time-fn | |
1331 (lambda (node) | |
1332 (newsticker--decode-iso8601-date | |
1333 (car (xml-node-children | |
1334 (car (xml-get-children node 'dc:date)))))) | |
1335 ;; guid-fn | |
1336 (lambda (node) | |
1337 nil) | |
1338 ;; extra-fn | |
1339 (lambda (node) | |
1340 (xml-node-children node)))) | |
1341 (or has-new-items is-new-feed))) | |
1342 | |
1343 (defun newsticker--parse-rss-2.0 (name time topnode) | |
1344 "Parse RSS 2.0 data. | |
1345 Return value as well as arguments NAME, TIME, and TOPNODE are the | |
1346 same as in `newsticker--parse-atom-1.0'. | |
1347 | |
1348 For the RSS 2.0 specification see http://blogs.law.harvard.edu/tech/rss." | |
1349 (newsticker--debug-msg "Parsing RSS 2.0 feed %s" name) | |
1350 (let* ((channelnode (car (xml-get-children topnode 'channel))) | |
1351 is-new-feed has-new-items) | |
1352 (setq is-new-feed (newsticker--parse-generic-feed | |
1353 name time | |
1354 ;; title | |
1355 (car (xml-node-children | |
1356 (car (xml-get-children channelnode 'title)))) | |
1357 ;; desc | |
1358 (car (xml-node-children | |
1359 (car (xml-get-children channelnode | |
1360 'description)))) | |
1361 ;; link | |
1362 (car (xml-node-children | |
1363 (car (xml-get-children channelnode 'link)))) | |
1364 ;; extra-elements | |
1365 (xml-node-children channelnode))) | |
1366 (setq has-new-items (newsticker--parse-generic-items | |
1367 name time (xml-get-children channelnode 'item) | |
1368 ;; title-fn | |
1369 (lambda (node) | |
1370 (car (xml-node-children | |
1371 (car (xml-get-children node 'title))))) | |
1372 ;; desc-fn | |
1373 (lambda (node) | |
1374 (or (car (xml-node-children | |
1375 (car (xml-get-children node | |
1376 'content:encoded)))) | |
1377 (car (xml-node-children | |
1378 (car (xml-get-children node | |
1379 'description)))))) | |
1380 ;; link-fn | |
1381 (lambda (node) | |
1382 (car (xml-node-children | |
1383 (car (xml-get-children node 'link))))) | |
1384 ;; time-fn | |
1385 (lambda (node) | |
1386 (newsticker--decode-rfc822-date | |
1387 (car (xml-node-children | |
1388 (car (xml-get-children node 'pubDate)))))) | |
1389 ;; guid-fn | |
1390 (lambda (node) | |
1391 (newsticker--guid-to-string | |
1392 (assoc 'guid (xml-node-children node)))) | |
1393 ;; extra-fn | |
1394 (lambda (node) | |
1395 (xml-node-children node)))) | |
1396 (or has-new-items is-new-feed))) | |
1397 | |
1398 (defun newsticker--parse-generic-feed (name time title desc link | |
1399 extra-elements) | |
1400 "Parse generic news feed data. | |
1401 Argument NAME gives the name of a news feed. TIME gives the | |
1402 system time at which the data have been retrieved. | |
1403 | |
1404 The arguments TITLE, DESC, LINK, and EXTRA-ELEMENTS give the feed's title, | |
1405 description, link, and extra elements resp." | |
1406 (let ((title (or title "[untitled]")) | |
1407 (link (or link "")) | |
1408 (old-item nil) | |
1409 (position 0) | |
1410 (something-was-added nil)) | |
1411 ;; decode numeric entities | |
1412 (setq title (newsticker--decode-numeric-entities title)) | |
1413 (setq desc (newsticker--decode-numeric-entities desc)) | |
1414 (setq link (newsticker--decode-numeric-entities link)) | |
1415 ;; remove whitespace from title, desc, and link | |
1416 (setq title (newsticker--remove-whitespace title)) | |
1417 (setq desc (newsticker--remove-whitespace desc)) | |
1418 (setq link (newsticker--remove-whitespace link)) | |
1419 | |
1420 ;; handle the feed itself | |
1421 (unless (newsticker--cache-contains newsticker--cache | |
1422 (intern name) title | |
1423 desc link 'feed) | |
1424 (setq something-was-added t)) | |
1425 (setq newsticker--cache | |
1426 (newsticker--cache-add newsticker--cache (intern name) | |
1427 title desc link time 'feed position | |
1428 extra-elements time 'feed)) | |
1429 something-was-added)) | |
1430 | |
1431 (defun newsticker--parse-generic-items (name time itemlist | |
1432 title-fn desc-fn | |
1433 link-fn time-fn | |
1434 guid-fn extra-fn) | |
1435 "Parse generic news feed data. | |
1436 Argument NAME gives the name of a news feed. TIME gives the | |
1437 system time at which the data have been retrieved. ITEMLIST | |
1438 contains the news items returned by the xml parser. | |
1439 | |
1440 The arguments TITLE-FN, DESC-FN, LINK-FN, TIME-FN, GUID-FN, and | |
1441 EXTRA-FN give functions for extracting title, description, link, | |
1442 time, guid, and extra-elements resp. They are called with one | |
1443 argument, which is one of the items in ITEMLIST." | |
1444 (let (title desc link | |
1445 (old-item nil) | |
1446 (position 0) | |
1447 (something-was-added nil)) | |
1448 ;; gather all items for this feed | |
1449 (mapc (lambda (node) | |
1450 (setq position (1+ position)) | |
1451 (setq title (or (funcall title-fn node) "[untitled]")) | |
1452 (setq desc (funcall desc-fn node)) | |
1453 (setq link (or (funcall link-fn node) "")) | |
1454 (setq time (or (funcall time-fn node) time)) | |
1455 ;; It happened that the title or description | |
1456 ;; contained evil HTML code that confused the | |
1457 ;; xml parser. Therefore: | |
1458 (unless (stringp title) | |
1459 (setq title (prin1-to-string title))) | |
1460 (unless (or (stringp desc) (not desc)) | |
1461 (setq desc (prin1-to-string desc))) | |
1462 ;; ignore items with empty title AND empty desc | |
1463 (when (or (> (length title) 0) | |
1464 (> (length desc) 0)) | |
1465 ;; decode numeric entities | |
1466 (setq title (newsticker--decode-numeric-entities title)) | |
1467 (when desc | |
1468 (setq desc (newsticker--decode-numeric-entities desc))) | |
1469 (setq link (newsticker--decode-numeric-entities link)) | |
1470 ;; remove whitespace from title, desc, and link | |
1471 (setq title (newsticker--remove-whitespace title)) | |
1472 (setq desc (newsticker--remove-whitespace desc)) | |
1473 (setq link (newsticker--remove-whitespace link)) | |
1474 ;; add data to cache | |
1475 ;; do we have this item already? | |
1476 (let* ((guid (funcall guid-fn node))) | |
1477 ;;(message "guid=%s" guid) | |
1478 (setq old-item | |
1479 (newsticker--cache-contains newsticker--cache | |
1480 (intern name) title | |
1481 desc link nil guid))) | |
1482 ;; add this item, or mark it as old, or do nothing | |
1483 (let ((age1 'new) | |
1484 (age2 'old) | |
1485 (item-new-p nil)) | |
1486 (if old-item | |
1487 (let ((prev-age (newsticker--age old-item))) | |
1488 (unless newsticker-automatically-mark-items-as-old | |
1489 ;; Some feeds deliver items multiply, the | |
1490 ;; first time we find an 'obsolete-old one the | |
1491 ;; cache, the following times we find an 'old | |
1492 ;; one | |
1493 (if (memq prev-age '(obsolete-old old)) | |
1494 (setq age2 'old) | |
1495 (setq age2 'new))) | |
1496 (if (eq prev-age 'immortal) | |
1497 (setq age2 'immortal)) | |
1498 (setq time (newsticker--time old-item))) | |
1499 ;; item was not there | |
1500 (setq item-new-p t) | |
1501 (setq something-was-added t)) | |
1502 (setq newsticker--cache | |
1503 (newsticker--cache-add | |
1504 newsticker--cache (intern name) title desc link | |
1505 time age1 position (funcall extra-fn node) | |
1506 time age2)) | |
1507 (when item-new-p | |
1508 (let ((item (newsticker--cache-contains | |
1509 newsticker--cache (intern name) title | |
1510 desc link nil))) | |
1511 (if newsticker-auto-mark-filter-list | |
1512 (newsticker--run-auto-mark-filter name item)) | |
1513 (run-hook-with-args | |
1514 'newsticker-new-item-functions name item)))))) | |
1515 itemlist) | |
1516 something-was-added)) | |
1517 | |
1518 ;; ====================================================================== | |
1519 ;;; Misc | |
1520 ;; ====================================================================== | |
1521 (defun newsticker--decode-numeric-entities (string) | |
1522 "Decode SGML numeric entities by their respective utf characters. | |
1523 This function replaces numeric entities in the input STRING and | |
1524 returns the modified string. For example \"*\" gets replaced | |
1525 by \"*\"." | |
1526 (if (and string (stringp string)) | |
1527 (let ((start 0)) | |
1528 (while (string-match "&#\\([0-9]+\\);" string start) | |
1529 (condition-case nil | |
1530 (setq string (replace-match | |
1531 (string (read (substring string | |
1532 (match-beginning 1) | |
1533 (match-end 1)))) | |
1534 nil nil string)) | |
1535 (error nil)) | |
1536 (setq start (1+ (match-beginning 0)))) | |
1537 string) | |
1538 nil)) | |
1539 | |
1540 (defun newsticker--remove-whitespace (string) | |
1541 "Remove leading and trailing whitespace from STRING." | |
1542 ;; we must have ...+ but not ...* in the regexps otherwise xemacs loops | |
1543 ;; endlessly... | |
1544 (when (and string (stringp string)) | |
1545 (replace-regexp-in-string | |
1546 "[ \t\r\n]+$" "" | |
1547 (replace-regexp-in-string "^[ \t\r\n]+" "" string)))) | |
1548 | |
1549 (defun newsticker--do-forget-preformatted (item) | |
1550 "Forget pre-formatted data for ITEM. | |
1551 Remove the pre-formatted from `newsticker--cache'." | |
1552 (if (nthcdr 7 item) | |
1553 (setcar (nthcdr 7 item) nil)) | |
1554 (if (nthcdr 6 item) | |
1555 (setcar (nthcdr 6 item) nil))) | |
1556 | |
1557 (defun newsticker--forget-preformatted () | |
1558 "Forget all cached pre-formatted data. | |
1559 Remove the pre-formatted from `newsticker--cache'." | |
1560 (mapc (lambda (feed) | |
1561 (mapc 'newsticker--do-forget-preformatted | |
1562 (cdr feed))) | |
1563 newsticker--cache) | |
1564 (when (fboundp 'newsticker--buffer-set-uptodate) | |
1565 (newsticker--buffer-set-uptodate nil))) | |
1566 | |
1567 (defun newsticker--debug-msg (string &rest args) | |
1568 "Print newsticker debug messages. | |
1569 This function calls `message' with arguments STRING and ARGS, if | |
1570 `newsticker-debug' is non-nil." | |
1571 (and newsticker-debug | |
1572 ;;(not (active-minibuffer-window)) | |
1573 ;;(not (current-message)) | |
1574 (apply 'message string args))) | |
1575 | |
1576 (defun newsticker--decode-iso8601-date (iso8601-string) | |
1577 "Return ISO8601-STRING in format like `decode-time'. | |
1578 Converts from ISO-8601 to Emacs representation. | |
1579 Examples: | |
1580 2004-09-17T05:09:49.001+00:00 | |
1581 2004-09-17T05:09:49+00:00 | |
1582 2004-09-17T05:09+00:00 | |
1583 2004-09-17T05:09:49 | |
1584 2004-09-17T05:09 | |
1585 2004-09-17 | |
1586 2004-09 | |
1587 2004" | |
1588 (if iso8601-string | |
1589 (when (string-match | |
1590 (concat | |
1591 "^ *\\([0-9]\\{4\\}\\)" ;year | |
1592 "\\(-\\([0-9]\\{2\\}\\)" ;month | |
1593 "\\(-\\([0-9]\\{2\\}\\)" ;day | |
1594 "\\(T" | |
1595 "\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" ;hour:minute | |
1596 "\\(:\\([0-9]\\{2\\}\\)\\(\\.[0-9]+\\)?\\)?" ;second | |
1597 ;timezone | |
1598 "\\(\\([-+Z]\\)\\(\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)?" | |
1599 "\\)?\\)?\\)? *$") | |
1600 iso8601-string) | |
1601 (let ((year (read (match-string 1 iso8601-string))) | |
1602 (month (read (or (match-string 3 iso8601-string) | |
1603 "1"))) | |
1604 (day (read (or (match-string 5 iso8601-string) | |
1605 "1"))) | |
1606 (hour (read (or (match-string 7 iso8601-string) | |
1607 "0"))) | |
1608 (minute (read (or (match-string 8 iso8601-string) | |
1609 "0"))) | |
1610 (second (read (or (match-string 10 iso8601-string) | |
1611 "0"))) | |
1612 (sign (match-string 13 iso8601-string)) | |
1613 (offset-hour (read (or (match-string 15 iso8601-string) | |
1614 "0"))) | |
1615 (offset-minute (read (or (match-string 16 iso8601-string) | |
1616 "0")))) | |
1617 (cond ((string= sign "+") | |
1618 (setq hour (- hour offset-hour)) | |
1619 (setq minute (- minute offset-minute))) | |
1620 ((string= sign "-") | |
1621 (setq hour (+ hour offset-hour)) | |
1622 (setq minute (+ minute offset-minute)))) | |
1623 ;; if UTC subtract current-time-zone offset | |
1624 ;;(setq second (+ (car (current-time-zone)) second))) | |
1625 | |
1626 (condition-case nil | |
1627 (encode-time second minute hour day month year t) | |
1628 (error | |
1629 (message "Cannot decode \"%s\"" iso8601-string) | |
1630 nil)))) | |
1631 nil)) | |
1632 | |
1633 (defun newsticker--decode-rfc822-date (rfc822-string) | |
1634 "Return RFC822-STRING in format like `decode-time'. | |
1635 Converts from RFC822 to Emacs representation. | |
1636 Examples: | |
1637 Sat, 07 September 2002 00:00:01 +0100 | |
1638 Sat, 07 September 2002 00:00:01 MET | |
1639 Sat, 07 Sep 2002 00:00:01 GMT | |
1640 07 Sep 2002 00:00:01 GMT | |
1641 07 Sep 2002" | |
1642 (if (and rfc822-string (stringp rfc822-string)) | |
1643 (when (string-match | |
1644 (concat | |
1645 "\\s-*" | |
1646 ;; week day | |
1647 "\\(\\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\)\\s-*,?\\)?\\s-*" | |
1648 ;; day | |
1649 "\\([0-9]\\{1,2\\}\\)\\s-+" | |
1650 ;; month | |
1651 "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|" | |
1652 "Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec\\).*?\\s-+" | |
1653 ;; year | |
1654 "\\([0-9]\\{2,4\\}\\)" | |
1655 ;; time may be missing | |
1656 "\\(\\s-+" | |
1657 ;; hour | |
1658 "\\([0-9]\\{2\\}\\)" | |
1659 ;; minute | |
1660 ":\\([0-9]\\{2\\}\\)" | |
1661 ;; second | |
1662 "\\(:\\([0-9]\\{2\\}\\)\\)?" | |
1663 ;; zone -- fixme | |
1664 "\\(\\s-+\\(" | |
1665 "UT\\|GMT\\|EST\\|EDT\\|CST\\|CDT\\|MST\\|MDT\\|PST\\|PDT" | |
1666 "\\|\\([-+]\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)" | |
1667 "\\)\\)?" | |
1668 "\\)?") | |
1669 rfc822-string) | |
1670 (let ((day (read (match-string 3 rfc822-string))) | |
1671 (month-name (match-string 4 rfc822-string)) | |
1672 (month 0) | |
1673 (year (read (match-string 5 rfc822-string))) | |
1674 (hour (read (or (match-string 7 rfc822-string) "0"))) | |
1675 (minute (read (or (match-string 8 rfc822-string) "0"))) | |
1676 (second (read (or (match-string 10 rfc822-string) "0"))) | |
1677 (zone (match-string 12 rfc822-string)) | |
1678 (sign (match-string 13 rfc822-string)) | |
1679 (offset-hour (read (or (match-string 14 rfc822-string) | |
1680 "0"))) | |
1681 (offset-minute (read (or (match-string 15 rfc822-string) | |
1682 "0"))) | |
1683 ;;FIXME | |
1684 ) | |
1685 (when zone | |
1686 (cond ((string= sign "+") | |
1687 (setq hour (- hour offset-hour)) | |
1688 (setq minute (- minute offset-minute))) | |
1689 ((string= sign "-") | |
1690 (setq hour (+ hour offset-hour)) | |
1691 (setq minute (+ minute offset-minute))))) | |
1692 (condition-case error-data | |
1693 (let ((i 1)) | |
1694 (mapc (lambda (m) | |
1695 (if (string= month-name m) | |
1696 (setq month i)) | |
1697 (setq i (1+ i))) | |
1698 '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" | |
1699 "Sep" "Oct" "Nov" "Dec")) | |
1700 (encode-time second minute hour day month year t)) | |
1701 (error | |
1702 (message "Cannot decode \"%s\": %s %s" rfc822-string | |
1703 (car error-data) (cdr error-data)) | |
1704 nil)))) | |
1705 nil)) | |
1706 | |
1707 (defun newsticker--lists-intersect-p (list1 list2) | |
1708 "Return t if LIST1 and LIST2 share elements." | |
1709 (let ((result nil)) | |
1710 (mapc (lambda (elt) | |
1711 (if (memq elt list2) | |
1712 (setq result t))) | |
1713 list1) | |
1714 result)) | |
1715 | |
1716 (defun newsticker--update-process-ids () | |
1717 "Update list of ids of active newsticker processes. | |
1718 Checks list of active processes against list of newsticker processes." | |
1719 (let ((active-procs (process-list)) | |
1720 (new-list nil)) | |
1721 (mapc (lambda (proc) | |
1722 (let ((id (process-id proc))) | |
1723 (if (memq id newsticker--process-ids) | |
1724 (setq new-list (cons id new-list))))) | |
1725 active-procs) | |
1726 (setq newsticker--process-ids new-list)) | |
1727 (force-mode-line-update)) | |
1728 | |
1729 ;; ====================================================================== | |
1730 ;;; Images | |
1731 ;; ====================================================================== | |
1732 (defun newsticker--image-get (feed-name url) | |
1733 "Get image of the news site FEED-NAME from URL. | |
1734 If the image has been downloaded in the last 24h do nothing." | |
1735 (let ((image-name (concat newsticker-imagecache-dirname "/" | |
1736 feed-name))) | |
1737 (if (and (file-exists-p image-name) | |
1738 (time-less-p (current-time) | |
1739 (time-add (nth 5 (file-attributes image-name)) | |
1740 (seconds-to-time 86400)))) | |
1741 (newsticker--debug-msg "%s: Getting image for %s skipped" | |
1742 (format-time-string "%A, %H:%M" (current-time)) | |
1743 feed-name) | |
1744 ;; download | |
1745 (newsticker--debug-msg "%s: Getting image for %s" | |
1746 (format-time-string "%A, %H:%M" (current-time)) | |
1747 feed-name) | |
1748 (let* ((buffername (concat " *newsticker-wget-image-" feed-name "*")) | |
1749 (item (or (assoc feed-name newsticker-url-list) | |
1750 (assoc feed-name newsticker-url-list-defaults) | |
1751 (error | |
1752 "Cannot get news for %s: Check newsticker-url-list" | |
1753 feed-name))) | |
1754 (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) | |
1755 newsticker-wget-arguments))) | |
1756 (save-excursion | |
1757 (set-buffer (get-buffer-create buffername)) | |
1758 (erase-buffer) | |
1759 ;; throw an error if there is an old wget-process around | |
1760 (if (get-process feed-name) | |
1761 (error "Another wget-process is running for image %s" | |
1762 feed-name)) | |
1763 ;; start wget | |
1764 (let* ((args (append wget-arguments (list url))) | |
1765 (proc (apply 'start-process feed-name buffername | |
1766 newsticker-wget-name args))) | |
1767 (set-process-coding-system proc 'no-conversion 'no-conversion) | |
1768 (set-process-sentinel proc 'newsticker--image-sentinel))))))) | |
1769 | |
1770 (defun newsticker--image-sentinel (process event) | |
1771 "Sentinel for image-retrieving PROCESS caused by EVENT." | |
1772 (let* ((p-status (process-status process)) | |
1773 (exit-status (process-exit-status process)) | |
1774 (feed-name (process-name process))) | |
1775 ;; catch known errors (zombie processes, rubbish-xml, etc.) | |
1776 ;; if an error occurs the news feed is not updated! | |
1777 (catch 'oops | |
1778 (unless (and (eq p-status 'exit) | |
1779 (= exit-status 0)) | |
1780 (message "%s: Error while retrieving image from %s" | |
1781 (format-time-string "%A, %H:%M" (current-time)) | |
1782 feed-name) | |
1783 (throw 'oops nil)) | |
1784 (let (image-name) | |
1785 (save-excursion | |
1786 (set-buffer (process-buffer process)) | |
1787 (setq image-name (concat newsticker-imagecache-dirname "/" | |
1788 feed-name)) | |
1789 (set-buffer-file-coding-system 'no-conversion) | |
1790 ;; make sure the cache dir exists | |
1791 (unless (file-directory-p newsticker-imagecache-dirname) | |
1792 (make-directory newsticker-imagecache-dirname)) | |
1793 ;; write and close buffer | |
1794 (let ((require-final-newline nil) | |
1795 (backup-inhibited t) | |
1796 (coding-system-for-write 'no-conversion)) | |
1797 (write-region nil nil image-name nil 'quiet)) | |
1798 (set-buffer-modified-p nil) | |
1799 (kill-buffer (current-buffer))))))) | |
1800 | |
1801 | |
1802 | |
1803 (defun newsticker--insert-image (img string) | |
1804 "Insert IMG with STRING at point." | |
1805 (insert-image img string)) | |
1806 | |
1807 ;; ====================================================================== | |
1808 ;;; HTML rendering | |
1809 ;; ====================================================================== | |
1810 (defun newsticker-htmlr-render (pos1 pos2) ; | |
1811 "Replacement for `htmlr-render'. | |
1812 Renders the HTML code in the region POS1 to POS2 using htmlr." | |
1813 (let ((str (buffer-substring-no-properties pos1 pos2))) | |
1814 (delete-region pos1 pos2) | |
1815 (insert | |
1816 (with-temp-buffer | |
1817 (insert str) | |
1818 (goto-char (point-min)) | |
1819 ;; begin original htmlr-render | |
1820 (when (fboundp 'htmlr-reset) (htmlr-reset)) | |
1821 ;; something omitted here... | |
1822 (when (fboundp 'htmlr-step) | |
1823 (while (< (point) (point-max)) | |
1824 (htmlr-step))) | |
1825 ;; end original htmlr-render | |
1826 (newsticker--remove-whitespace (buffer-string)))))) | |
1827 | |
1828 ;; ====================================================================== | |
1829 ;;; Manipulation of cached data | |
1830 ;; ====================================================================== | |
1831 (defun newsticker--cache-set-preformatted-contents (item contents) | |
1832 "Set preformatted contents of ITEM to CONTENTS." | |
1833 (if (nthcdr 6 item) | |
1834 (setcar (nthcdr 6 item) contents) | |
1835 (setcdr (nthcdr 5 item) (list contents)))) | |
1836 | |
1837 (defun newsticker--cache-set-preformatted-title (item title) | |
1838 "Set preformatted title of ITEM to TITLE." | |
1839 (if (nthcdr 7 item) | |
1840 (setcar (nthcdr 7 item) title) | |
1841 (setcdr (nthcdr 6 item) title))) | |
1842 | |
1843 (defun newsticker--cache-replace-age (data feed old-age new-age) | |
1844 "Mark all items in DATA in FEED which carry age OLD-AGE with NEW-AGE. | |
1845 If FEED is 'any it applies to all feeds. If OLD-AGE is 'any, | |
1846 all marks are replaced by NEW-AGE. Removes all pre-formatted contents." | |
1847 (mapc (lambda (a-feed) | |
1848 (when (or (eq feed 'any) | |
1849 (eq (car a-feed) feed)) | |
1850 (let ((items (cdr a-feed))) | |
1851 (mapc (lambda (item) | |
1852 (when (or (eq old-age 'any) | |
1853 (eq (newsticker--age item) old-age)) | |
1854 (setcar (nthcdr 4 item) new-age) | |
1855 (newsticker--do-forget-preformatted item))) | |
1856 items)))) | |
1857 data) | |
1858 data) | |
1859 | |
1860 (defun newsticker--cache-mark-expired (data feed old-age new-age time) | |
1861 "Mark all expired entries. | |
1862 This function sets the age entries in DATA in the feed FEED. If | |
1863 an item's age is OLD-AGE it is set to NEW-AGE if the item is | |
1864 older than TIME." | |
1865 (mapc | |
1866 (lambda (a-feed) | |
1867 (when (or (eq feed 'any) | |
1868 (eq (car a-feed) feed)) | |
1869 (let ((items (cdr a-feed))) | |
1870 (mapc | |
1871 (lambda (item) | |
1872 (when (eq (newsticker--age item) old-age) | |
1873 (let ((exp-time (time-add (newsticker--time item) | |
1874 (seconds-to-time time)))) | |
1875 (when (time-less-p exp-time (current-time)) | |
1876 (newsticker--debug-msg | |
1877 "Item `%s' from %s has expired on %s" | |
1878 (newsticker--title item) | |
1879 (format-time-string "%Y-%02m-%d, %H:%M" | |
1880 (newsticker--time item)) | |
1881 (format-time-string "%Y-%02m-%d, %H:%M" exp-time)) | |
1882 (setcar (nthcdr 4 item) new-age))))) | |
1883 items)))) | |
1884 data) | |
1885 data) | |
1886 | |
1887 (defun newsticker--cache-contains (data feed title desc link age | |
1888 &optional guid) | |
1889 "Check DATA whether FEED contains an item with the given properties. | |
1890 This function returns the contained item or nil if it is not | |
1891 contained. | |
1892 The properties which are checked are TITLE, DESC, LINK, AGE, and | |
1893 GUID. In general all properties must match in order to return a | |
1894 certain item, except for the following cases. | |
1895 | |
1896 If AGE equals 'feed the TITLE, DESCription and LINK do not | |
1897 matter. If DESC is nil it is ignored as well. If | |
1898 `newsticker-desc-comp-max' is non-nil, only the first | |
1899 `newsticker-desc-comp-max' characters of DESC are taken into | |
1900 account. | |
1901 | |
1902 If GUID is non-nil it is sufficient to match this value, and the | |
1903 other properties are ignored." | |
1904 ;;(newsticker--debug-msg "Looking for %s guid=%s" title guid) | |
1905 (condition-case nil | |
1906 (catch 'found | |
1907 (when (and desc newsticker-desc-comp-max | |
1908 (> (length desc) newsticker-desc-comp-max)) | |
1909 (setq desc (substring desc 0 newsticker-desc-comp-max))) | |
1910 (mapc | |
1911 (lambda (this-feed) | |
1912 (when (eq (car this-feed) feed) | |
1913 (mapc (lambda (anitem) | |
1914 (when (cond (guid | |
1915 ;; global unique id can match | |
1916 (string= guid (newsticker--guid anitem))) | |
1917 (t;;FIXME? | |
1918 (or | |
1919 ;; or title, desc, etc. | |
1920 (and | |
1921 ;;(or (not (eq age 'feed)) | |
1922 ;; (eq (newsticker--age anitem) 'feed)) | |
1923 (string= (newsticker--title anitem) | |
1924 title) | |
1925 (or (not link) | |
1926 (string= (newsticker--link anitem) | |
1927 link)) | |
1928 (or (not desc) | |
1929 (if (and desc newsticker-desc-comp-max | |
1930 (> (length (newsticker--desc | |
1931 anitem)) | |
1932 newsticker-desc-comp-max)) | |
1933 (string= (substring | |
1934 (newsticker--desc anitem) | |
1935 0 | |
1936 newsticker-desc-comp-max) | |
1937 desc) | |
1938 (string= (newsticker--desc anitem) | |
1939 desc))))))) | |
1940 ;;(newsticker--debug-msg "Found %s guid=%s" | |
1941 ;; (newsticker--title anitem) | |
1942 ;; (newsticker--guid anitem)) | |
1943 (throw 'found anitem))) | |
1944 (cdr this-feed)))) | |
1945 data) | |
1946 ;;(newsticker--debug-msg "Found nothing") | |
1947 nil) | |
1948 (error nil))) | |
1949 | |
1950 (defun newsticker--cache-add (data feed-name-symbol title desc link time age | |
1951 position extra-elements | |
1952 &optional updated-time updated-age | |
1953 preformatted-contents | |
1954 preformatted-title) | |
1955 "Add another item to cache data. | |
1956 Add to DATA in the FEED-NAME-SYMBOL an item with TITLE, DESC, | |
1957 LINK, TIME, AGE, POSITION, and EXTRA-ELEMENTS. If this item is | |
1958 contained already, its time is set to UPDATED-TIME, its mark is | |
1959 set to UPDATED-AGE, and its pre-formatted contents is set to | |
1960 PREFORMATTED-CONTENTS and PREFORMATTED-TITLE. Returns the age | |
1961 which the item got." | |
1962 (let* ((guid (newsticker--guid-to-string (assoc 'guid extra-elements))) | |
1963 (item (newsticker--cache-contains data feed-name-symbol title desc link | |
1964 age guid))) | |
1965 ;;(message "guid=%s" guid) | |
1966 (if item | |
1967 ;; does exist already -- change age, update time and position | |
1968 (progn | |
1969 ;;(newsticker--debug-msg "Updating item %s %s %s %s %s -> %s %s | |
1970 ;; (guid %s -> %s)" | |
1971 ;; feed-name-symbol title link time age | |
1972 ;; updated-time updated-age | |
1973 ;; guid (newsticker--guid item)) | |
1974 (if (nthcdr 5 item) | |
1975 (setcar (nthcdr 5 item) position) | |
1976 (setcdr (nthcdr 4 item) (list position))) | |
1977 (setcar (nthcdr 4 item) updated-age) | |
1978 (if updated-time | |
1979 (setcar (nthcdr 3 item) updated-time)) | |
1980 ;; replace cached pre-formatted contents | |
1981 (newsticker--cache-set-preformatted-contents | |
1982 item preformatted-contents) | |
1983 (newsticker--cache-set-preformatted-title | |
1984 item preformatted-title)) | |
1985 ;; did not exist or age equals 'feed-name-symbol | |
1986 (setq item (list title desc link time age position preformatted-contents | |
1987 preformatted-title extra-elements)) | |
1988 ;;(newsticker--debug-msg "Adding item %s" item) | |
1989 (catch 'found | |
1990 (mapc (lambda (this-feed) | |
1991 (when (eq (car this-feed) feed-name-symbol) | |
1992 (setcdr this-feed (nconc (cdr this-feed) (list item))) | |
1993 (throw 'found this-feed))) | |
1994 data) | |
1995 ;; the feed is not contained | |
1996 (add-to-list 'data (list feed-name-symbol item) t)))) | |
1997 data) | |
1998 | |
1999 (defun newsticker--cache-remove (data feed-symbol age) | |
2000 "Remove all entries from DATA in the feed FEED-SYMBOL with AGE. | |
2001 FEED-SYMBOL may be 'any. Entries from old feeds, which are no longer in | |
2002 `newsticker-url-list' or `newsticker-url-list-defaults', are removed as | |
2003 well." | |
2004 (let* ((pos data) | |
2005 (feed (car pos)) | |
2006 (last-pos nil)) | |
2007 (while feed | |
2008 (if (or (assoc (symbol-name (car feed)) newsticker-url-list) | |
2009 (assoc (symbol-name (car feed)) newsticker-url-list-defaults)) | |
2010 ;; feed is still valid=active | |
2011 ;; (message "Keeping feed %s" (car feed)) | |
2012 (if (or (eq feed-symbol 'any) | |
2013 (eq feed-symbol (car feed))) | |
2014 (let* ((item-pos (cdr feed)) | |
2015 (item (car item-pos)) | |
2016 (prev-pos nil)) | |
2017 (while item | |
2018 ;;(message "%s" (car item)) | |
2019 (if (eq age (newsticker--age item)) | |
2020 ;; remove this item | |
2021 (progn | |
2022 ;;(message "Removing item %s" (car item)) | |
2023 (if prev-pos | |
2024 (setcdr prev-pos (cdr item-pos)) | |
2025 (setcdr feed (cdr item-pos)))) | |
2026 ;;(message "Keeping item %s" (car item)) | |
2027 (setq prev-pos item-pos)) | |
2028 (setq item-pos (cdr item-pos)) | |
2029 (setq item (car item-pos))))) | |
2030 ;; feed is not active anymore | |
2031 ;; (message "Removing feed %s" (car feed)) | |
2032 (if last-pos | |
2033 (setcdr last-pos (cdr pos)) | |
2034 (setq data (cdr pos)))) | |
2035 (setq last-pos pos) | |
2036 (setq pos (cdr pos)) | |
2037 (setq feed (car pos))))) | |
2038 | |
2039 ;; ====================================================================== | |
2040 ;;; Sorting | |
2041 ;; ====================================================================== | |
2042 (defun newsticker--cache-item-compare-by-time (item1 item2) | |
2043 "Compare two news items ITEM1 and ITEM2 by comparing their time values." | |
2044 (catch 'result | |
2045 (let ((age1 (newsticker--age item1)) | |
2046 (age2 (newsticker--age item2))) | |
2047 (if (not (eq age1 age2)) | |
2048 (cond ((eq age1 'obsolete) | |
2049 (throw 'result nil)) | |
2050 ((eq age2 'obsolete) | |
2051 (throw 'result t))))) | |
2052 (let* ((time1 (newsticker--time item1)) | |
2053 (time2 (newsticker--time item2))) | |
2054 (cond ((< (nth 0 time1) (nth 0 time2)) | |
2055 nil) | |
2056 ((> (nth 0 time1) (nth 0 time2)) | |
2057 t) | |
2058 ((< (nth 1 time1) (nth 1 time2)) | |
2059 nil) | |
2060 ((> (nth 1 time1) (nth 1 time2)) | |
2061 t) | |
2062 ((< (or (nth 2 time1) 0) (or (nth 2 time2) 0)) | |
2063 nil) | |
2064 ((> (or (nth 2 time1) 0) (or (nth 2 time2) 0)) | |
2065 t) | |
2066 (t | |
2067 nil))))) | |
2068 | |
2069 (defun newsticker--cache-item-compare-by-title (item1 item2) | |
2070 "Compare ITEM1 and ITEM2 by comparing their titles." | |
2071 (catch 'result | |
2072 (let ((age1 (newsticker--age item1)) | |
2073 (age2 (newsticker--age item2))) | |
2074 (if (not (eq age1 age2)) | |
2075 (cond ((eq age1 'obsolete) | |
2076 (throw 'result nil)) | |
2077 ((eq age2 'obsolete) | |
2078 (throw 'result t))))) | |
2079 (string< (newsticker--title item1) (newsticker--title item2)))) | |
2080 | |
2081 (defun newsticker--cache-item-compare-by-position (item1 item2) | |
2082 "Compare ITEM1 and ITEM2 by comparing their original positions." | |
2083 (catch 'result | |
2084 (let ((age1 (newsticker--age item1)) | |
2085 (age2 (newsticker--age item2))) | |
2086 (if (not (eq age1 age2)) | |
2087 (cond ((eq age1 'obsolete) | |
2088 (throw 'result nil)) | |
2089 ((eq age2 'obsolete) | |
2090 (throw 'result t))))) | |
2091 (< (or (newsticker--pos item1) 0) (or (newsticker--pos item2) 0)))) | |
2092 | |
2093 | |
2094 | |
2095 (defun newsticker--cache-save () | |
2096 "Update and save newsticker cache file." | |
2097 (interactive) | |
2098 (newsticker--cache-update t)) | |
2099 | |
2100 (defun newsticker--cache-update (&optional save) | |
2101 "Update newsticker cache file. | |
2102 If optional argument SAVE is not nil the cache file is saved to disk." | |
2103 (save-excursion | |
2104 (let ((coding-system-for-write 'utf-8)) | |
2105 (with-temp-buffer | |
2106 (setq buffer-undo-list t) | |
2107 (erase-buffer) | |
2108 (insert ";; -*- coding: utf-8 -*-\n") | |
2109 (insert (prin1-to-string newsticker--cache)) | |
2110 (when save | |
2111 (set-visited-file-name newsticker-cache-filename) | |
2112 (save-buffer)))))) | |
2113 | |
2114 (defun newsticker--cache-get-feed (feed) | |
2115 "Return the cached data for the feed FEED. | |
2116 FEED is a symbol!" | |
2117 (assoc feed newsticker--cache)) | |
2118 | |
2119 ;; ====================================================================== | |
2120 ;;; Statistics | |
2121 ;; ====================================================================== | |
2122 (defun newsticker--stat-num-items (feed &rest ages) | |
2123 "Return number of items in the given FEED which have one of the given AGES. | |
2124 If AGES is nil, the total number of items is returned." | |
2125 (let ((items (cdr (newsticker--cache-get-feed feed))) | |
2126 (num 0)) | |
2127 (while items | |
2128 (if ages | |
2129 (if (memq (newsticker--age (car items)) ages) | |
2130 (setq num (1+ num))) | |
2131 (if (memq (newsticker--age (car items)) '(new old immortal obsolete)) | |
2132 (setq num (1+ num)))) | |
2133 (setq items (cdr items))) | |
2134 num)) | |
2135 | |
2136 (defun newsticker--stat-num-items-total (&optional age) | |
2137 "Return total number of items in all feeds which have the given AGE. | |
2138 If AGE is nil, the total number of items is returned." | |
2139 (apply '+ | |
2140 (mapcar (lambda (feed) | |
2141 (if age | |
2142 (newsticker--stat-num-items (intern (car feed)) age) | |
2143 (newsticker--stat-num-items (intern (car feed))))) | |
2144 (append newsticker-url-list-defaults newsticker-url-list)))) | |
2145 | |
2146 ;; ====================================================================== | |
2147 ;;; OPML | |
2148 ;; ====================================================================== | |
2149 (defun newsticker-opml-export () | |
2150 "OPML subscription export. | |
2151 Export subscriptions to a buffer in OPML Format." | |
2152 (interactive) | |
2153 (with-current-buffer (get-buffer-create "*OPML Export*") | |
2154 (set-buffer-file-coding-system 'utf-8) | |
2155 (insert (concat | |
2156 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
2157 "<!-- OPML generated by Emacs newsticker.el -->\n" | |
2158 "<opml version=\"1.0\">\n" | |
2159 " <head>\n" | |
2160 " <title>mySubscriptions</title>\n" | |
2161 " <dateCreated>" (format-time-string "%a, %d %b %Y %T %z") | |
2162 "</dateCreated>\n" | |
2163 " <ownerEmail>" user-mail-address "</ownerEmail>\n" | |
2164 " <ownerName>" (user-full-name) "</ownerName>\n" | |
2165 " </head>\n" | |
2166 " <body>\n")) | |
2167 (mapc (lambda (sub) | |
2168 (insert " <outline text=\"") | |
2169 (insert (newsticker--title sub)) | |
2170 (insert "\" xmlUrl=\"") | |
2171 (insert (cadr sub)) | |
2172 (insert "\"/>\n")) | |
2173 (append newsticker-url-list newsticker-url-list-defaults)) | |
2174 (insert " </body>\n</opml>\n")) | |
2175 (pop-to-buffer "*OPML Export*") | |
2176 (when (fboundp 'sgml-mode) | |
2177 (sgml-mode))) | |
2178 | |
2179 (defun newsticker--opml-import-outlines (outlines) | |
2180 "Recursively import OUTLINES from OPML data. | |
2181 Note that nested outlines are currently flattened -- i.e. grouping is | |
2182 removed." | |
2183 (mapc (lambda (outline) | |
2184 (let ((name (xml-get-attribute outline 'text)) | |
2185 (url (xml-get-attribute outline 'xmlUrl)) | |
2186 (children (xml-get-children outline 'outline))) | |
2187 (unless (string= "" url) | |
2188 (add-to-list 'newsticker-url-list | |
2189 (list name url nil nil nil) t)) | |
2190 (if children | |
2191 (newsticker--opml-import-outlines children)))) | |
2192 outlines)) | |
2193 | |
2194 (defun newsticker-opml-import (filename) | |
2195 "Import OPML data from FILENAME." | |
2196 (interactive "fOPML file: ") | |
2197 (set-buffer (find-file-noselect filename)) | |
2198 (goto-char (point-min)) | |
2199 (let* ((node-list (xml-parse-region (point-min) (point-max))) | |
2200 (body (car (xml-get-children (car node-list) 'body))) | |
2201 (outlines (xml-get-children body 'outline))) | |
2202 (newsticker--opml-import-outlines outlines)) | |
2203 (customize-variable 'newsticker-url-list)) | |
2204 | |
2205 ;; ====================================================================== | |
2206 ;;; Auto marking | |
2207 ;; ====================================================================== | |
2208 (defun newsticker--run-auto-mark-filter (feed item) | |
2209 "Automatically mark an item as old or immortal. | |
2210 This function checks the variable `newsticker-auto-mark-filter-list' | |
2211 for an entry that matches FEED and ITEM." | |
2212 (let ((case-fold-search t)) | |
2213 (mapc (lambda (filter) | |
2214 (let ((filter-feed (car filter)) | |
2215 (pattern-list (cadr filter))) | |
2216 (when (string-match filter-feed feed) | |
2217 (newsticker--do-run-auto-mark-filter item pattern-list)))) | |
2218 newsticker-auto-mark-filter-list))) | |
2219 | |
2220 (defun newsticker--do-run-auto-mark-filter (item list) | |
2221 "Actually compare ITEM against the pattern-LIST. | |
2222 LIST must be an element of `newsticker-auto-mark-filter-list'." | |
2223 (mapc (lambda (pattern) | |
2224 (let ((age (nth 0 pattern)) | |
2225 (place (nth 1 pattern)) | |
2226 (regexp (nth 2 pattern)) | |
2227 (title (newsticker--title item)) | |
2228 (desc (newsticker--desc item))) | |
2229 (when (or (eq place 'title) (eq place 'all)) | |
2230 (when (and title (string-match regexp title)) | |
2231 (newsticker--debug-msg "Auto-marking as %s: `%s'" | |
2232 age (newsticker--title item)) | |
2233 (setcar (nthcdr 4 item) age))) | |
2234 (when (or (eq place 'description) (eq place 'all)) | |
2235 (when (and desc (string-match regexp desc)) | |
2236 (newsticker--debug-msg "Auto-marking as %s: `%s'" | |
2237 age (newsticker--title item)) | |
2238 (setcar (nthcdr 4 item) age))))) | |
2239 list)) | |
2240 | |
2241 | |
2242 ;; ====================================================================== | |
2243 ;;; Hook samples | |
2244 ;; ====================================================================== | |
2245 (defun newsticker-new-item-functions-sample (feed item) | |
2246 "Demonstrate the use of the `newsticker-new-item-functions' hook. | |
2247 This function just prints out the values of the FEED and title of the ITEM." | |
2248 (message (concat "newsticker-new-item-functions-sample: feed=`%s', " | |
2249 "title=`%s'") | |
2250 feed (newsticker--title item))) | |
2251 | |
2252 (defun newsticker-download-images (feed item) | |
2253 "Download the first image. | |
2254 If FEED equals \"imagefeed\" download the first image URL found | |
2255 in the description=contents of ITEM to the directory | |
2256 \"~/tmp/newsticker/FEED/TITLE\" where TITLE is the title of the item." | |
2257 (when (string= feed "imagefeed") | |
2258 (let ((title (newsticker--title item)) | |
2259 (desc (newsticker--desc item))) | |
2260 (when (string-match "<img src=\"\\(http://[^ \"]+\\)\"" desc) | |
2261 (let ((url (substring desc (match-beginning 1) (match-end 1))) | |
2262 (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) | |
2263 (org-dir default-directory)) | |
2264 (unless (file-directory-p temp-dir) | |
2265 (make-directory temp-dir t)) | |
2266 (cd temp-dir) | |
2267 (message "Getting image %s" url) | |
2268 (apply 'start-process "wget-image" | |
2269 " *newsticker-wget-download-images*" | |
2270 newsticker-wget-name | |
2271 (list url)) | |
2272 (cd org-dir)))))) | |
2273 | |
2274 (defun newsticker-download-enclosures (feed item) | |
2275 "In all FEEDs download the enclosed object of the news ITEM. | |
2276 The object is saved to the directory \"~/tmp/newsticker/FEED/TITLE\", which | |
2277 is created if it does not exist. TITLE is the title of the news | |
2278 item. Argument FEED is ignored. | |
2279 This function is suited for adding it to `newsticker-new-item-functions'." | |
2280 (let ((title (newsticker--title item)) | |
2281 (enclosure (newsticker--enclosure item))) | |
2282 (when enclosure | |
2283 (let ((url (cdr (assoc 'url enclosure))) | |
2284 (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) | |
2285 (org-dir default-directory)) | |
2286 (unless (file-directory-p temp-dir) | |
2287 (make-directory temp-dir t)) | |
2288 (cd temp-dir) | |
2289 (message "Getting enclosure %s" url) | |
2290 (apply 'start-process "wget-enclosure" | |
2291 " *newsticker-wget-download-enclosures*" | |
2292 newsticker-wget-name | |
2293 (list url)) | |
2294 (cd org-dir))))) | |
2295 | |
2296 ;; ====================================================================== | |
2297 ;;; Retrieve samples | |
2298 ;; ====================================================================== | |
2299 (defun newsticker-retrieve-random-message (feed-name) | |
2300 "Return an artificial RSS string under the name FEED-NAME." | |
2301 (concat "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?><rss version=\"0.91\">" | |
2302 "<channel>" | |
2303 "<title>newsticker-retrieve-random-message</title>" | |
2304 "<description>Sample retrieval function</description>" | |
2305 "<pubDate>FIXME Sat, 07 Sep 2005 00:00:01 GMT</pubDate>" | |
2306 "<item><title>" (format "Your lucky number is %d" (random 10000)) | |
2307 "</title><description>" (format "Or maybe it is %d" (random 10000)) | |
2308 "</description></item></channel></rss>")) | |
2309 | |
2310 (provide 'newsticker-backend) | |
2311 | |
95684 | 2312 ;; arch-tag: 0e37b658-56e9-49ab-90f9-f2df57e1a659 |
95675 | 2313 ;;; newsticker-backend.el ends here |