Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / nnweb.el
1 ;;; nnweb.el --- retrieving articles via web search engines
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; Note: You need to have `url' and `w3' installed for this
28 ;; backend to work.
29
30 ;;; Code:
31
32 (eval-when-compile (require 'cl))
33 (eval-when-compile (require 'gnus-clfns))
34
35 (require 'nnoo)
36 (require 'message)
37 (require 'gnus-util)
38 (require 'gnus)
39 (require 'nnmail)
40 (eval-when-compile
41   (ignore-errors
42     (require 'w3)
43     (require 'url)
44     (require 'w3-forms)))
45
46 ;; Report failure to find w3 at load time if appropriate.
47 (unless noninteractive
48   (eval '(progn
49            (require 'w3)
50            (require 'url)
51            (require 'w3-forms))))
52
53 (nnoo-declare nnweb)
54
55 (defvoo nnweb-directory (nnheader-concat gnus-directory "nnweb/")
56   "Where nnweb will save its files.")
57
58 (defvoo nnweb-type 'dejanews
59   "What search engine type is being used.
60 Valid types include `dejanews', `dejanewsold', `reference',
61 and `altavista'.")
62
63 (defvar nnweb-type-definition
64   '(
65     (dejanews ;; bought by google.com
66      (article . nnweb-google-wash-article)
67      (id . "http://groups.google.com/groups?as_umsgid=%s")
68      (map . nnweb-google-create-mapping)
69      (search . nnweb-google-search)
70      (address . "http://groups.google.com/groups")
71      (identifier . nnweb-google-identity))
72 ;;;     (dejanews
73 ;;;      (article . ignore)
74 ;;;      (id . "http://search.dejanews.com/msgid.xp?MID=%s&fmt=text")
75 ;;;      (map . nnweb-dejanews-create-mapping)
76 ;;;      (search . nnweb-dejanews-search)
77 ;;;      (address . "http://www.deja.com/=dnc/qs.xp")
78 ;;;      (identifier . nnweb-dejanews-identity))
79 ;;;     (dejanewsold
80 ;;;      (article . ignore)
81 ;;;      (map . nnweb-dejanews-create-mapping)
82 ;;;      (search . nnweb-dejanewsold-search)
83 ;;;      (address . "http://www.deja.com/dnquery.xp")
84 ;;;      (identifier . nnweb-dejanews-identity))
85     (reference
86      (article . nnweb-reference-wash-article)
87      (map . nnweb-reference-create-mapping)
88      (search . nnweb-reference-search)
89      (address . "http://www.reference.com/cgi-bin/pn/go")
90      (identifier . identity))
91     (altavista
92      (article . nnweb-altavista-wash-article)
93      (map . nnweb-altavista-create-mapping)
94      (search . nnweb-altavista-search)
95      (address . "http://www.altavista.digital.com/cgi-bin/query")
96      (id . "/cgi-bin/news?id@%s")
97      (identifier . identity)))
98   "Type-definition alist.")
99
100 (defvoo nnweb-search nil
101   "Search string to feed to DejaNews.")
102
103 (defvoo nnweb-max-hits 999
104   "Maximum number of hits to display.")
105
106 (defvoo nnweb-ephemeral-p nil
107   "Whether this nnweb server is ephemeral.")
108
109 ;;; Internal variables
110
111 (defvoo nnweb-articles nil)
112 (defvoo nnweb-buffer nil)
113 (defvoo nnweb-group-alist nil)
114 (defvoo nnweb-group nil)
115 (defvoo nnweb-hashtb nil)
116
117 ;;; Interface functions
118
119 (nnoo-define-basics nnweb)
120
121 (deffoo nnweb-retrieve-headers (articles &optional group server fetch-old)
122   (nnweb-possibly-change-server group server)
123   (save-excursion
124     (set-buffer nntp-server-buffer)
125     (erase-buffer)
126     (let (article header)
127       (mm-with-unibyte-current-buffer
128         (while (setq article (pop articles))
129           (when (setq header (cadr (assq article nnweb-articles)))
130             (nnheader-insert-nov header))))
131       'nov)))
132
133 (deffoo nnweb-request-scan (&optional group server)
134   (nnweb-possibly-change-server group server)
135   (funcall (nnweb-definition 'map))
136   (unless nnweb-ephemeral-p
137     (nnweb-write-active)
138     (nnweb-write-overview group)))
139
140 (deffoo nnweb-request-group (group &optional server dont-check)
141   (nnweb-possibly-change-server nil server)
142   (when (and group
143              (not (equal group nnweb-group))
144              (not nnweb-ephemeral-p))
145     (let ((info (assoc group nnweb-group-alist)))
146       (when info
147         (setq nnweb-group group)
148         (setq nnweb-type (nth 2 info))
149         (setq nnweb-search (nth 3 info))
150         (unless dont-check
151           (nnweb-read-overview group)))))
152   (cond
153    ((not nnweb-articles)
154     (nnheader-report 'nnweb "No matching articles"))
155    (t
156     (let ((active (if nnweb-ephemeral-p
157                       (cons (caar nnweb-articles)
158                             (caar (last nnweb-articles)))
159                     (cadr (assoc group nnweb-group-alist)))))
160       (nnheader-report 'nnweb "Opened group %s" group)
161       (nnheader-insert
162        "211 %d %d %d %s\n" (length nnweb-articles)
163        (car active) (cdr active) group)))))
164
165 (deffoo nnweb-close-group (group &optional server)
166   (nnweb-possibly-change-server group server)
167   (when (gnus-buffer-live-p nnweb-buffer)
168     (save-excursion
169       (set-buffer nnweb-buffer)
170       (set-buffer-modified-p nil)
171       (kill-buffer nnweb-buffer)))
172   t)
173
174 (deffoo nnweb-request-article (article &optional group server buffer)
175   (nnweb-possibly-change-server group server)
176   (save-excursion
177     (set-buffer (or buffer nntp-server-buffer))
178     (let* ((header (cadr (assq article nnweb-articles)))
179            (url (and header (mail-header-xref header))))
180       (when (or (and url
181                      (mm-with-unibyte-current-buffer
182                        (nnweb-fetch-url url)))
183                 (and (stringp article)
184                      (nnweb-definition 'id t)
185                      (let ((fetch (nnweb-definition 'id))
186                            art)
187                        (when (string-match "^<\\(.*\\)>$" article)
188                          (setq art (match-string 1 article)))
189                        (and fetch
190                             art
191                             (mm-with-unibyte-current-buffer
192                               (nnweb-fetch-url
193                                (format fetch article)))))))
194         (unless nnheader-callback-function
195           (funcall (nnweb-definition 'article))
196           (nnweb-decode-entities))
197         (nnheader-report 'nnweb "Fetched article %s" article)
198         (cons group (and (numberp article) article))))))
199
200 (deffoo nnweb-close-server (&optional server)
201   (when (and (nnweb-server-opened server)
202              (gnus-buffer-live-p nnweb-buffer))
203     (save-excursion
204       (set-buffer nnweb-buffer)
205       (set-buffer-modified-p nil)
206       (kill-buffer nnweb-buffer)))
207   (nnoo-close-server 'nnweb server))
208
209 (deffoo nnweb-request-list (&optional server)
210   (nnweb-possibly-change-server nil server)
211   (save-excursion
212     (set-buffer nntp-server-buffer)
213     (nnmail-generate-active nnweb-group-alist)
214     t))
215
216 (deffoo nnweb-request-update-info (group info &optional server)
217   (nnweb-possibly-change-server group server))
218
219 (deffoo nnweb-asynchronous-p ()
220   t)
221
222 (deffoo nnweb-request-create-group (group &optional server args)
223   (nnweb-possibly-change-server nil server)
224   (nnweb-request-delete-group group)
225   (push `(,group ,(cons 1 0) ,@args) nnweb-group-alist)
226   (nnweb-write-active)
227   t)
228
229 (deffoo nnweb-request-delete-group (group &optional force server)
230   (nnweb-possibly-change-server group server)
231   (gnus-pull group nnweb-group-alist t)
232   (nnweb-write-active)
233   (gnus-delete-file (nnweb-overview-file group))
234   t)
235
236 (nnoo-define-skeleton nnweb)
237
238 ;;; Internal functions
239
240 (defun nnweb-read-overview (group)
241   "Read the overview of GROUP and build the map."
242   (when (file-exists-p (nnweb-overview-file group))
243     (mm-with-unibyte-buffer
244       (nnheader-insert-file-contents (nnweb-overview-file group))
245       (goto-char (point-min))
246       (let (header)
247         (while (not (eobp))
248           (setq header (nnheader-parse-nov))
249           (forward-line 1)
250           (push (list (mail-header-number header)
251                       header (mail-header-xref header))
252                 nnweb-articles)
253           (nnweb-set-hashtb header (car nnweb-articles)))))))
254
255 (defun nnweb-write-overview (group)
256   "Write the overview file for GROUP."
257   (with-temp-file (nnweb-overview-file group)
258     (let ((articles nnweb-articles))
259       (while articles
260         (nnheader-insert-nov (cadr (pop articles)))))))
261
262 (defun nnweb-set-hashtb (header data)
263   (gnus-sethash (nnweb-identifier (mail-header-xref header))
264                 data nnweb-hashtb))
265
266 (defun nnweb-get-hashtb (url)
267   (gnus-gethash (nnweb-identifier url) nnweb-hashtb))
268
269 (defun nnweb-identifier (ident)
270   (funcall (nnweb-definition 'identifier) ident))
271
272 (defun nnweb-overview-file (group)
273   "Return the name of the overview file of GROUP."
274   (nnheader-concat nnweb-directory group ".overview"))
275
276 (defun nnweb-write-active ()
277   "Save the active file."
278   (gnus-make-directory nnweb-directory)
279   (with-temp-file (nnheader-concat nnweb-directory "active")
280     (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
281
282 (defun nnweb-read-active ()
283   "Read the active file."
284   (load (nnheader-concat nnweb-directory "active") t t t))
285
286 (defun nnweb-definition (type &optional noerror)
287   "Return the definition of TYPE."
288   (let ((def (cdr (assq type (assq nnweb-type nnweb-type-definition)))))
289     (when (and (not def)
290                (not noerror))
291       (error "Undefined definition %s" type))
292     def))
293
294 (defun nnweb-possibly-change-server (&optional group server)
295   (nnweb-init server)
296   (when server
297     (unless (nnweb-server-opened server)
298       (nnweb-open-server server)))
299   (unless nnweb-group-alist
300     (nnweb-read-active))
301   (when group
302     (when (and (not nnweb-ephemeral-p)
303                (not (equal group nnweb-group)))
304       (setq nnweb-hashtb (gnus-make-hashtable 4095))
305       (nnweb-request-group group nil t))))
306
307 (defun nnweb-init (server)
308   "Initialize buffers and such."
309   (unless (gnus-buffer-live-p nnweb-buffer)
310     (setq nnweb-buffer
311           (save-excursion
312             (mm-with-unibyte
313               (nnheader-set-temp-buffer
314                (format " *nnweb %s %s %s*"
315                        nnweb-type nnweb-search server))
316               (current-buffer))))))
317
318 (defun nnweb-fetch-url (url)
319   (let (buf)
320     (save-excursion
321       (if (not nnheader-callback-function)
322           (progn
323             (with-temp-buffer
324               (mm-enable-multibyte)
325               (let ((coding-system-for-read 'binary)
326                     (coding-system-for-write 'binary)
327                     (input-coding-system 'binary)
328                     (output-coding-system 'binary)
329                     (default-process-coding-system 'binary))
330                 (nnweb-insert url))
331               (setq buf (buffer-string)))
332             (erase-buffer)
333             (insert buf)
334             t)
335         (nnweb-url-retrieve-asynch
336          url 'nnweb-callback (current-buffer) nnheader-callback-function)
337         t))))
338
339 (defun nnweb-callback (buffer callback)
340   (when (gnus-buffer-live-p url-working-buffer)
341     (save-excursion
342       (set-buffer url-working-buffer)
343       (funcall (nnweb-definition 'article))
344       (nnweb-decode-entities)
345       (set-buffer buffer)
346       (goto-char (point-max))
347       (insert-buffer-substring url-working-buffer))
348     (funcall callback t)
349     (gnus-kill-buffer url-working-buffer)))
350
351 (defun nnweb-url-retrieve-asynch (url callback &rest data)
352   (let ((url-request-method "GET")
353         (old-asynch url-be-asynchronous)
354         (url-request-data nil)
355         (url-request-extra-headers nil)
356         (url-working-buffer (generate-new-buffer-name " *nnweb*")))
357     (setq-default url-be-asynchronous t)
358     (save-excursion
359       (set-buffer (get-buffer-create url-working-buffer))
360       (setq url-current-callback-data data
361             url-be-asynchronous t
362             url-current-callback-func callback)
363       (url-retrieve url nil))
364     (setq-default url-be-asynchronous old-asynch)))
365
366 (if (fboundp 'url-retrieve-synchronously)
367     (defun nnweb-url-retrieve-asynch (url callback &rest data)
368       (url-retrieve url callback data)))
369
370 ;;;
371 ;;; DejaNews functions.
372 ;;;
373
374 (defun nnweb-dejanews-create-mapping ()
375   "Perform the search and create an number-to-url alist."
376   (save-excursion
377     (set-buffer nnweb-buffer)
378     (erase-buffer)
379     (when (funcall (nnweb-definition 'search) nnweb-search)
380       (let ((i 0)
381             (more t)
382             (case-fold-search t)
383             (active (or (cadr (assoc nnweb-group nnweb-group-alist))
384                         (cons 1 0)))
385             subject date from
386             map url parse a table group text)
387         (while more
388           ;; Go through all the article hits on this page.
389           (goto-char (point-min))
390           (setq parse (w3-parse-buffer (current-buffer))
391                 table (nth 1 (nnweb-parse-find-all 'table parse)))
392           (dolist (row (nth 2 (car (nth 2 table))))
393             (setq a (nnweb-parse-find 'a row)
394                   url (cdr (assq 'href (nth 1 a)))
395                   text (nreverse (nnweb-text row)))
396             (when a
397               (setq subject (nth 4 text)
398                     group (nth 2 text)
399                     date (nth 1 text)
400                     from (nth 0 text))
401               (if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date)
402                   (setq date (format "%s %s 00:00:00 %s"
403                                      (car (rassq (string-to-number
404                                                   (match-string 2 date))
405                                                  parse-time-months))
406                                      (match-string 3 date)
407                                      (match-string 1 date)))
408                 (setq date "Jan 1 00:00:00 0000"))
409               (incf i)
410               (setq url (concat url "&fmt=text"))
411               (when (string-match "&context=[^&]+" url)
412                 (setq url (replace-match "" t t url)))
413               (unless (nnweb-get-hashtb url)
414                 (push
415                  (list
416                   (incf (cdr active))
417                   (make-full-mail-header
418                    (cdr active) (concat subject " (" group ")") from date
419                    (concat "<" (nnweb-identifier url) "@dejanews>")
420                    nil 0 0 url))
421                  map)
422                 (nnweb-set-hashtb (cadar map) (car map)))))
423           ;; See whether there is a "Get next 20 hits" button here.
424           (goto-char (point-min))
425           (if (or (not (re-search-forward
426                         "HREF=\"\\([^\"]+\\)\"[<>b]+Next result" nil t))
427                   (>= i nnweb-max-hits))
428               (setq more nil)
429             ;; Yup -- fetch it.
430             (setq more (match-string 1))
431             (erase-buffer)
432             (url-insert-file-contents more)))
433         ;; Return the articles in the right order.
434         (setq nnweb-articles
435               (sort (nconc nnweb-articles map) 'car-less-than-car))))))
436
437 (defun nnweb-dejanews-search (search)
438   (nnweb-insert
439    (concat
440     (nnweb-definition 'address)
441     "?"
442     (nnweb-encode-www-form-urlencoded
443      `(("ST" . "PS")
444        ("svcclass" . "dnyr")
445        ("QRY" . ,search)
446        ("defaultOp" . "AND")
447        ("DBS" . "1")
448        ("OP" . "dnquery.xp")
449        ("LNG" . "ALL")
450        ("maxhits" . "100")
451        ("threaded" . "0")
452        ("format" . "verbose2")
453        ("showsort" . "date")
454        ("agesign" . "1")
455        ("ageweight" . "1")))))
456   t)
457
458 (defun nnweb-dejanewsold-search (search)
459   (nnweb-fetch-form
460    (nnweb-definition 'address)
461    `(("query" . ,search)
462      ("defaultOp" . "AND")
463      ("svcclass" . "dnold")
464      ("maxhits" . "100")
465      ("format" . "verbose2")
466      ("threaded" . "0")
467      ("showsort" . "date")
468      ("agesign" . "1")
469      ("ageweight" . "1")))
470   t)
471
472 (defun nnweb-dejanews-identity (url)
473   "Return an unique identifier based on URL."
474   (if (string-match "AN=\\([0-9]+\\)" url)
475       (match-string 1 url)
476     url))
477
478 ;;;
479 ;;; InReference
480 ;;;
481
482 (defun nnweb-reference-create-mapping ()
483   "Perform the search and create an number-to-url alist."
484   (save-excursion
485     (set-buffer nnweb-buffer)
486     (erase-buffer)
487     (when (funcall (nnweb-definition 'search) nnweb-search)
488       (let ((i 0)
489             (more t)
490             (case-fold-search t)
491             (active (or (cadr (assoc nnweb-group nnweb-group-alist))
492                         (cons 1 0)))
493             Subject Score Date Newsgroups From Message-ID
494             map url)
495         (while more
496           ;; Go through all the article hits on this page.
497           (goto-char (point-min))
498           (search-forward "</pre><hr>" nil t)
499           (delete-region (point-min) (point))
500           (goto-char (point-min))
501           (while (re-search-forward "^ +[0-9]+\\." nil t)
502             (narrow-to-region
503              (point)
504              (if (re-search-forward "^$" nil t)
505                  (match-beginning 0)
506                (point-max)))
507             (goto-char (point-min))
508             (when (looking-at ".*href=\"\\([^\"]+\\)\"")
509               (setq url (match-string 1)))
510             (nnweb-remove-markup)
511             (goto-char (point-min))
512             (while (search-forward "\t" nil t)
513               (replace-match " "))
514             (goto-char (point-min))
515             (while (re-search-forward "^\\([^:]+\\): \\(.*\\)$" nil t)
516               (set (intern (match-string 1)) (match-string 2)))
517             (widen)
518             (search-forward "</pre>" nil t)
519             (incf i)
520             (unless (nnweb-get-hashtb url)
521               (push
522                (list
523                 (incf (cdr active))
524                 (make-full-mail-header
525                  (cdr active) (concat  "(" Newsgroups ") " Subject) From Date
526                  Message-ID
527                  nil 0 (string-to-int Score) url))
528                map)
529               (nnweb-set-hashtb (cadar map) (car map))))
530           (setq more nil))
531         ;; Return the articles in the right order.
532         (setq nnweb-articles
533               (sort (nconc nnweb-articles map) 'car-less-than-car))))))
534
535 (defun nnweb-reference-wash-article ()
536   (let ((case-fold-search t))
537     (goto-char (point-min))
538     (re-search-forward "^</center><hr>" nil t)
539     (delete-region (point-min) (point))
540     (search-forward "<pre>" nil t)
541     (forward-line -1)
542     (let ((body (point-marker)))
543       (search-forward "</pre>" nil t)
544       (delete-region (point) (point-max))
545       (nnweb-remove-markup)
546       (goto-char (point-min))
547       (while (looking-at " *$")
548         (gnus-delete-line))
549       (narrow-to-region (point-min) body)
550       (while (and (re-search-forward "^$" nil t)
551                   (not (eobp)))
552         (gnus-delete-line))
553       (goto-char (point-min))
554       (while (looking-at "\\(^[^ ]+:\\) *")
555         (replace-match "\\1 " t)
556         (forward-line 1))
557       (goto-char (point-min))
558       (when (re-search-forward "^References:" nil t)
559         (narrow-to-region
560          (point) (if (re-search-forward "^$\\|^[^:]+:" nil t)
561                      (match-beginning 0)
562                    (point-max)))
563         (goto-char (point-min))
564         (while (not (eobp))
565           (unless (looking-at "References")
566             (insert "\t")
567             (forward-line 1)))
568         (goto-char (point-min))
569         (while (search-forward "," nil t)
570           (replace-match " " t t)))
571       (widen)
572       (set-marker body nil))))
573
574 (defun nnweb-reference-search (search)
575   (url-insert-file-contents
576    (concat
577     (nnweb-definition 'address)
578     "?"
579     (nnweb-encode-www-form-urlencoded
580      `(("search" . "advanced")
581        ("querytext" . ,search)
582        ("subj" . "")
583        ("name" . "")
584        ("login" . "")
585        ("host" . "")
586        ("organization" . "")
587        ("groups" . "")
588        ("keywords" . "")
589        ("choice" . "Search")
590        ("startmonth" . "Jul")
591        ("startday" . "25")
592        ("startyear" . "1996")
593        ("endmonth" . "Aug")
594        ("endday" . "24")
595        ("endyear" . "1996")
596        ("mode" . "Quick")
597        ("verbosity" . "Verbose")
598        ("ranking" . "Relevance")
599        ("first" . "1")
600        ("last" . "25")
601        ("score" . "50")))))
602   (setq buffer-file-name nil)
603   t)
604
605 ;;;
606 ;;; Alta Vista
607 ;;;
608
609 (defun nnweb-altavista-create-mapping ()
610   "Perform the search and create an number-to-url alist."
611   (save-excursion
612     (set-buffer nnweb-buffer)
613     (erase-buffer)
614     (let ((part 0))
615       (when (funcall (nnweb-definition 'search) nnweb-search part)
616         (let ((i 0)
617               (more t)
618               (case-fold-search t)
619               (active (or (cadr (assoc nnweb-group nnweb-group-alist))
620                           (cons 1 0)))
621               subject date from id group
622               map url)
623           (while more
624             ;; Go through all the article hits on this page.
625             (goto-char (point-min))
626             (search-forward "<dt>" nil t)
627             (delete-region (point-min) (match-beginning 0))
628             (goto-char (point-min))
629             (while (search-forward "<dt>" nil t)
630               (replace-match "\n<blubb>"))
631             (nnweb-decode-entities)
632             (goto-char (point-min))
633             (while (re-search-forward "<blubb>.*href=\"\\([^\"]+\\)\"><strong>\\([^>]*\\)</strong></a><dd>\\([^-]+\\)- <b>\\([^<]+\\)<.*href=\"news:\\([^\"]+\\)\">.*\">\\(.+\\)</a><P>"
634                                       nil t)
635               (setq url (match-string 1)
636                     subject (match-string 2)
637                     date (match-string 3)
638                     group (match-string 4)
639                     id (concat "<" (match-string 5) ">")
640                     from (match-string 6))
641               (incf i)
642               (unless (nnweb-get-hashtb url)
643                 (push
644                  (list
645                   (incf (cdr active))
646                   (make-full-mail-header
647                    (cdr active) (concat  "(" group ") " subject) from date
648                    id nil 0 0 url))
649                  map)
650                 (nnweb-set-hashtb (cadar map) (car map))))
651             ;; See if we want more.
652             (when (or (not nnweb-articles)
653                       (>= i nnweb-max-hits)
654                       (not (funcall (nnweb-definition 'search)
655                                     nnweb-search (incf part))))
656               (setq more nil)))
657           ;; Return the articles in the right order.
658           (setq nnweb-articles
659                 (sort (nconc nnweb-articles map) 'car-less-than-car)))))))
660
661 (defun nnweb-altavista-wash-article ()
662   (goto-char (point-min))
663   (let ((case-fold-search t))
664     (when (re-search-forward "^<strong>" nil t)
665       (delete-region (point-min) (match-beginning 0)))
666     (goto-char (point-min))
667     (while (looking-at "<strong>\\([^ ]+\\) +</strong> +\\(.*\\)$")
668       (replace-match "\\1: \\2" t)
669       (forward-line 1))
670     (when (re-search-backward "^References:" nil t)
671       (narrow-to-region (point) (progn (forward-line 1) (point)))
672       (goto-char (point-min))
673       (while (re-search-forward "<A.*\\?id@\\([^\"]+\\)\">[0-9]+</A>" nil t)
674         (replace-match "&lt;\\1&gt; " t)))
675     (widen)
676     (nnweb-remove-markup)))
677
678 (defun nnweb-altavista-search (search &optional part)
679   (url-insert-file-contents
680    (concat
681     (nnweb-definition 'address)
682     "?"
683     (nnweb-encode-www-form-urlencoded
684      `(("pg" . "aq")
685        ("what" . "news")
686        ,@(when part `(("stq" . ,(int-to-string (* part 30)))))
687        ("fmt" . "d")
688        ("q" . ,search)
689        ("r" . "")
690        ("d0" . "")
691        ("d1" . "")))))
692   (setq buffer-file-name nil)
693   t)
694
695 ;;;
696 ;;; Deja bought by google.com
697 ;;;
698
699 (defun nnweb-google-wash-article ()
700  (let ((case-fold-search t))
701     (goto-char (point-min))
702     (re-search-forward "^<pre>" nil t)
703     (narrow-to-region (point-min) (point))
704     (search-backward "</table>" nil t 2)
705     (delete-region (point-min) (point))
706     (if (search-forward "[view thread]" nil t)
707         (replace-match ""))
708     (goto-char (point-min))
709     (while (search-forward "<br>" nil t)
710       (replace-match "\n"))
711     (nnweb-remove-markup)
712     (nnweb-decode-entities)
713     (while (re-search-forward "^[ \t]*\n" nil t)
714       (replace-match ""))
715     (goto-char (point-max))
716     (insert "\n")
717     (widen)
718     (narrow-to-region (point) (point-max))
719     (search-forward "</pre>" nil t)
720     (delete-region (point) (point-max))
721     (nnweb-remove-markup)
722     (nnweb-decode-entities)
723     (widen)))
724
725 (defun nnweb-google-create-mapping ()
726   "Perform the search and create an number-to-url alist."
727   (save-excursion
728     (set-buffer nnweb-buffer)
729     (erase-buffer)
730     (when (funcall (nnweb-definition 'search) nnweb-search)
731       (let ((i 0)
732             (more t)
733             (case-fold-search t)
734             (active (or (cadr (assoc nnweb-group nnweb-group-alist))
735                         (cons 1 0)))
736             Subject Score Date Newsgroups From Message-ID
737             map url)
738         (while more
739           ;; Go through all the article hits on this page.
740           (goto-char (point-min))
741           (while (re-search-forward
742                   "a href=/groups\\(\\?[^ \">]*seld=[0-9]+[^ \">]*\\)" nil t)
743             (setq url
744                   (concat (nnweb-definition 'address)
745                           (match-string 1)))
746             (narrow-to-region (search-forward ">" nil t)
747                               (search-forward "</a>" nil t))
748             (nnweb-remove-markup)
749             (nnweb-decode-entities)
750             (setq Subject (buffer-string))
751             (goto-char (point-max))
752             (widen)
753             (narrow-to-region (re-search-forward "<a[^>]+>" nil t)
754                               (search-forward "</a>" nil t))
755             (nnweb-remove-markup)
756             (nnweb-decode-entities)
757             (setq Newsgroups (buffer-string))
758             (goto-char (point-max))
759             (widen)
760             (when (looking-at
761                    "[ \t]*-[ \t]*\\([0-9]+/[A-Za-z]+/[0-9]+\\)[ \t]*by[ \t]*\\([^<]*\\) - <a")
762               (setq From (match-string 2)
763                     Date (match-string 1)))
764             (forward-line 1)
765             (incf i)
766             (unless (nnweb-get-hashtb url)
767               (push
768                (list
769                 (incf (cdr active))
770                 (make-full-mail-header
771                  (cdr active) (concat  "(" Newsgroups ") " Subject) From Date
772                  Message-ID
773                  nil 0 0 url))
774                map)
775               (nnweb-set-hashtb (cadar map) (car map))))
776           ;; FIXME: There is more.
777           (setq more nil))
778         ;; Return the articles in the right order.
779         (setq nnweb-articles
780               (sort (nconc nnweb-articles map) 'car-less-than-car))))))
781
782 (defun nnweb-google-search (search)
783   (nnweb-insert
784    (concat
785     (nnweb-definition 'address)
786     "?"
787     (nnweb-encode-www-form-urlencoded
788      `(("q" . ,search)
789        ("num". "100")
790        ("hq" . "")
791        ("hl" . "")
792        ("lr" . "")
793        ("safe" . "off")
794        ("sites" . "groups")))))
795   t)
796
797 (defun nnweb-google-identity (url)
798   "Return an unique identifier based on URL."
799   (if (string-match "seld=\\([0-9]+\\)" url)
800       (match-string 1 url)
801     url))
802
803 ;;;
804 ;;; General web/w3 interface utility functions
805 ;;;
806
807 (defun nnweb-insert-html (parse)
808   "Insert HTML based on a w3 parse tree."
809   (if (stringp parse)
810       (insert (nnheader-string-as-multibyte parse))
811     (insert "<" (symbol-name (car parse)) " ")
812     (insert (mapconcat
813              (lambda (param)
814                (concat (symbol-name (car param)) "="
815                        (prin1-to-string
816                         (if (consp (cdr param))
817                             (cadr param)
818                           (cdr param)))))
819              (nth 1 parse)
820              " "))
821     (insert ">\n")
822     (mapcar 'nnweb-insert-html (nth 2 parse))
823     (insert "</" (symbol-name (car parse)) ">\n")))
824
825 (defun nnweb-encode-www-form-urlencoded (pairs)
826   "Return PAIRS encoded for forms."
827   (mapconcat
828    (function
829     (lambda (data)
830       (concat (w3-form-encode-xwfu (car data)) "="
831               (w3-form-encode-xwfu (cdr data)))))
832    pairs "&"))
833
834 (defun nnweb-fetch-form (url pairs)
835   "Fetch a form from URL with PAIRS as the data using the POST method."
836   (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
837         (url-request-method "POST")
838         (url-request-extra-headers
839          '(("Content-type" . "application/x-www-form-urlencoded"))))
840     (url-insert-file-contents url)
841     (setq buffer-file-name nil))
842   t)
843
844 (defun nnweb-decode-entities ()
845   "Decode all HTML entities."
846   (goto-char (point-min))
847   (while (re-search-forward "&\\(#[0-9]+\\|[a-z]+\\);" nil t)
848     (let ((elem (if (eq (aref (match-string 1) 0) ?\#)
849                         (let ((c
850                                (string-to-number (substring
851                                                   (match-string 1) 1))))
852                           (if (mm-char-or-char-int-p c) c 32))
853                       (or (cdr (assq (intern (match-string 1))
854                                      w3-html-entities))
855                           ?#))))
856       (unless (stringp elem)
857         (setq elem (char-to-string elem)))
858       (replace-match elem t t))))
859
860 (defun nnweb-decode-entities-string (string)
861   (with-temp-buffer
862     (insert string)
863     (nnweb-decode-entities)
864     (buffer-substring (point-min) (point-max))))
865
866 (defun nnweb-remove-markup ()
867   "Remove all HTML markup, leaving just plain text."
868   (goto-char (point-min))
869   (while (search-forward "<!--" nil t)
870     (delete-region (match-beginning 0)
871                    (or (search-forward "-->" nil t)
872                        (point-max))))
873   (goto-char (point-min))
874   (while (re-search-forward "<[^>]+>" nil t)
875     (replace-match "" t t)))
876
877 (defun nnweb-insert (url &optional follow-refresh)
878   "Insert the contents from an URL in the current buffer.
879 If FOLLOW-REFRESH is non-nil, redirect refresh url in META."
880   (let ((name buffer-file-name))
881     (if follow-refresh
882         (save-restriction
883           (narrow-to-region (point) (point))
884           (url-insert-file-contents url)
885           (goto-char (point-min))
886           (when (re-search-forward
887                  "<meta[ \t\r\n]*http-equiv=\"Refresh\"[^>]*URL=\\([^\"]+\\)\"" nil t)
888             (let ((url (match-string 1)))
889               (delete-region (point-min) (point-max))
890               (nnweb-insert url t))))
891       (url-insert-file-contents url))
892     (setq buffer-file-name name)))
893
894 (defun nnweb-parse-find (type parse &optional maxdepth)
895   "Find the element of TYPE in PARSE."
896   (catch 'found
897     (nnweb-parse-find-1 type parse maxdepth)))
898
899 (defun nnweb-parse-find-1 (type contents maxdepth)
900   (when (or (null maxdepth)
901             (not (zerop maxdepth)))
902     (when (consp contents)
903       (when (eq (car contents) type)
904         (throw 'found contents))
905       (when (listp (cdr contents))
906         (dolist (element contents)
907           (when (consp element)
908             (nnweb-parse-find-1 type element
909                                 (and maxdepth (1- maxdepth)))))))))
910
911 (defun nnweb-parse-find-all (type parse)
912   "Find all elements of TYPE in PARSE."
913   (catch 'found
914     (nnweb-parse-find-all-1 type parse)))
915
916 (defun nnweb-parse-find-all-1 (type contents)
917   (let (result)
918     (when (consp contents)
919       (if (eq (car contents) type)
920           (push contents result)
921         (when (listp (cdr contents))
922           (dolist (element contents)
923             (when (consp element)
924               (setq result
925                     (nconc result (nnweb-parse-find-all-1 type element))))))))
926     result))
927
928 (defvar nnweb-text)
929 (defun nnweb-text (parse)
930   "Return a list of text contents in PARSE."
931   (let ((nnweb-text nil))
932     (nnweb-text-1 parse)
933     (nreverse nnweb-text)))
934
935 (defun nnweb-text-1 (contents)
936   (dolist (element contents)
937     (if (stringp element)
938         (push element nnweb-text)
939       (when (and (consp element)
940                  (listp (cdr element)))
941         (nnweb-text-1 element)))))
942
943 (defun nnweb-replace-in-string (string match newtext)
944   (while (string-match match string)
945     (setq string (replace-match newtext t t string)))
946   string)
947
948 (provide 'nnweb)
949
950 ;;; nnweb.el ends here