*** empty log message ***
[gnus] / lisp / nnweb.el
index a213475..c375d76 100644 (file)
@@ -1,7 +1,7 @@
 ;;; nnweb.el --- retrieving articles via web search engines
-;; Copyright (C) 1996,97 Free Software Foundation, Inc.
+;; Copyright (C) 1996,97,98,99 Free Software Foundation, Inc.
 
-;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
 
 ;; This file is part of GNU Emacs.
 
 ;;; Code:
 
+(eval-when-compile (require 'cl))
+
 (require 'nnoo)
 (require 'message)
 (require 'gnus-util)
 (require 'gnus)
-(require 'w3)
-(require 'url)
 (require 'nnmail)
-(ignore-errors
-  (require 'w3-forms))
+(require 'mm-util)
+(eval-when-compile
+  (ignore-errors
+    (require 'w3)
+    (require 'url)
+    (require 'w3-forms)))
+;; Report failure to find w3 at load time if appropriate.
+(eval '(progn
+        (require 'w3)
+        (require 'url)
+        (require 'w3-forms)))
 
 (nnoo-declare nnweb)
 
   "Where nnweb will save its files.")
 
 (defvoo nnweb-type 'dejanews
-  "What search engine type is being used.")
+  "What search engine type is being used.
+Valid types include `dejanews', `dejanewsold', `reference',
+and `altavista'.")
 
-(defvar nnweb-type-definition
+(defvoo nnweb-type-definition
   '((dejanews
      (article . nnweb-dejanews-wash-article)
      (map . nnweb-dejanews-create-mapping)
      (search . nnweb-dejanews-search)
-     (address . "http://xp9.dejanews.com/dnquery.xp")
+     (address . "http://x8.dejanews.com/dnquery.xp")
+     (identifier . nnweb-dejanews-identity))
+    (dejanewsold
+     (article . nnweb-dejanews-wash-article)
+     (map . nnweb-dejanews-create-mapping)
+     (search . nnweb-dejanewsold-search)
+     (address . "http://x8.dejanews.com/dnquery.xp")
      (identifier . nnweb-dejanews-identity))
     (reference
      (article . nnweb-reference-wash-article)
@@ -71,7 +88,7 @@
 (defvoo nnweb-search nil
   "Search string to feed to DejaNews.")
 
-(defvoo nnweb-max-hits 100
+(defvoo nnweb-max-hits 999
   "Maximum number of hits to display.")
 
 (defvoo nnweb-ephemeral-p nil
 
 (deffoo nnweb-request-scan (&optional group server)
   (nnweb-possibly-change-server group server)
+  (setq nnweb-hashtb (gnus-make-hashtable 4095))
   (funcall (nnweb-definition 'map))
   (unless nnweb-ephemeral-p
     (nnweb-write-active)
 
 (deffoo nnweb-close-group (group &optional server)
   (nnweb-possibly-change-server group server)
-  (when (buffer-live-p nnweb-buffer)
+  (when (gnus-buffer-live-p nnweb-buffer)
     (save-excursion
       (set-buffer nnweb-buffer)
       (set-buffer-modified-p nil)
 
 (deffoo nnweb-close-server (&optional server)
   (when (and (nnweb-server-opened server)
-            (buffer-live-p nnweb-buffer))
+            (gnus-buffer-live-p nnweb-buffer))
     (save-excursion
       (set-buffer nnweb-buffer)
       (set-buffer-modified-p nil)
 
 (deffoo nnweb-request-delete-group (group &optional force server)
   (nnweb-possibly-change-server group server)
-  (gnus-delete-assoc group nnweb-group-alist)
+  (gnus-pull group nnweb-group-alist t)
+  (nnweb-write-active)
   (gnus-delete-file (nnweb-overview-file group))
   t)
 
 (defun nnweb-read-overview (group)
   "Read the overview of GROUP and build the map."
   (when (file-exists-p (nnweb-overview-file group))
-    (nnheader-temp-write nil
+    (with-temp-buffer
       (nnheader-insert-file-contents (nnweb-overview-file group))
       (goto-char (point-min))
-      (setq nnweb-hashtb (gnus-make-hashtable
-                         (count-lines (point-min) (point-max))))
       (let (header)
        (while (not (eobp))
          (setq header (nnheader-parse-nov))
 
 (defun nnweb-write-overview (group)
   "Write the overview file for GROUP."
-  (nnheader-temp-write (nnweb-overview-file group)
+  (with-temp-file (nnweb-overview-file group)
     (let ((articles nnweb-articles))
       (while articles
        (nnheader-insert-nov (cadr (pop articles)))))))
 
 (defun nnweb-write-active ()
   "Save the active file."
-  (nnheader-temp-write (nnheader-concat nnweb-directory "active")
+  (with-temp-file (nnheader-concat nnweb-directory "active")
     (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
 
 (defun nnweb-read-active ()
 
 (defun nnweb-init (server)
   "Initialize buffers and such."
-  (unless (buffer-live-p nnweb-buffer)
+  (unless (gnus-buffer-live-p nnweb-buffer)
     (setq nnweb-buffer
          (save-excursion
            (nnheader-set-temp-buffer
          (save-excursion
            (set-buffer nnweb-buffer)
            (erase-buffer)
-           (prog1
-               (url-insert-file-contents url)
-             (copy-to-buffer buf (point-min) (point-max)))))
+           (url-insert-file-contents url)
+           (copy-to-buffer buf (point-min) (point-max))
+           t))
       (nnweb-url-retrieve-asynch
        url 'nnweb-callback (current-buffer) nnheader-callback-function)
       t)))
 
 (defun nnweb-callback (buffer callback)
-  (when (buffer-live-p url-working-buffer)
+  (when (gnus-buffer-live-p url-working-buffer)
     (save-excursion
       (set-buffer url-working-buffer)
       (funcall (nnweb-definition 'article))
       (url-retrieve url))
     (setq-default url-be-asynchronous old-asynch)))
 
-(defun nnweb-encode-www-form-urlencoded (pairs)
-  "Return PAIRS encoded for forms."
-  (mapconcat
-   (function
-    (lambda (data)
-      (concat (w3-form-encode-xwfu (car data)) "="
-             (w3-form-encode-xwfu (cdr data)))))
-   pairs "&"))
-
-(defun nnweb-fetch-form (url pairs)
-  (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
-       (url-request-method "POST")
-       (url-request-extra-headers
-        '(("Content-type" . "application/x-www-form-urlencoded"))))
-    (url-insert-file-contents url)
-    (setq buffer-file-name nil))
-  t)
-
-(defun nnweb-decode-entities ()
-  (goto-char (point-min))
-  (while (re-search-forward "&\\([a-z]+\\);" nil t)
-    (replace-match (char-to-string (or (cdr (assq (intern (match-string 1))
-                                                 w3-html-entities ))
-                                      ?#))
-                  t t)))
-
-(defun nnweb-remove-markup ()
-  (goto-char (point-min))
-  (while (search-forward "<!--" nil t)
-    (delete-region (match-beginning 0)
-                  (or (search-forward "-->" nil t)
-                      (point-max))))
-  (goto-char (point-min))
-  (while (re-search-forward "<[^>]+>" nil t)
-    (replace-match "" t t)))
-
 ;;;
 ;;; DejaNews functions.
 ;;;
            (case-fold-search t)
            (active (or (cadr (assoc nnweb-group nnweb-group-alist))
                        (cons 1 0)))
-           Subject Score Date Newsgroup Author
+           Subject (Score "0") Date Newsgroup Author
            map url)
        (while more
          ;; Go through all the article hits on this page.
          (goto-char (point-min))
          (nnweb-decode-entities)
          (goto-char (point-min))
-         (while (re-search-forward "^ +[0-9]+\\." nil t)
+         (while (re-search-forward "^ <P>\n" nil t)
            (narrow-to-region
             (point)
-            (cond ((re-search-forward "^ +[0-9]+\\." nil t)
+            (cond ((re-search-forward "^ <P>\n" nil t)
                    (match-beginning 0))
                   ((search-forward "\n\n" nil t)
                    (point))
                   (t
                    (point-max))))
            (goto-char (point-min))
-           (when (looking-at ".*HREF=\"\\([^\"]+\\)\"")
-             (setq url (match-string 1)))
-           (nnweb-remove-markup)
-           (goto-char (point-min))
-           (while (search-forward "\t" nil t)
-             (replace-match " "))
-           (goto-char (point-min))
-           (while (re-search-forward "^ +\\([^:]+\\): +\\(.*\\)$" nil t)
-             (set (intern (match-string 1)) (match-string 2)))
+           (looking-at ".*HREF=\"\\([^\"]+\\)\"\\(.*\\)")
+           (setq url (match-string 1))
+           (let ((begin (point)))
+             (nnweb-remove-markup)
+             (goto-char begin)
+             (while (search-forward "\t" nil t)
+               (replace-match " "))
+             (goto-char begin)
+             (end-of-line)
+             (setq Subject (buffer-substring begin (point)))
+             (if (re-search-forward
+                  "^ Newsgroup: \\(.*\\)\n Posted on \\([0-9/]+\\) by \\(.*\\)$" nil t)
+                 (setq Newsgroup (match-string 1)
+                       Date (match-string 2)
+                       Author (match-string 3))))
            (widen)
-           (when (string-match "#[0-9]+/[0-9]+ *$" Subject)
-             (setq Subject (substring Subject 0 (match-beginning 0))))
            (incf i)
            (unless (nnweb-get-hashtb url)
              (push
               (list
                (incf (cdr active))
                (make-full-mail-header
-                (cdr active) (concat  "(" Newsgroup ") " Subject) Author Date
+                (cdr active) Subject Author Date
                 (concat "<" (nnweb-identifier url) "@dejanews>")
                 nil 0 (string-to-int Score) url))
               map)
              (nnweb-set-hashtb (cadar map) (car map))))
          ;; See whether there is a "Get next 20 hits" button here.
          (if (or (not (re-search-forward
-                       "HREF=\"\\([^\"]+\\)\">Get next" nil t))
+                       "HREF=\"\\([^\"]+\\)\"[<>b]+Next result" nil t))
                  (>= i nnweb-max-hits))
              (setq more nil)
            ;; Yup -- fetch it.
            (url-insert-file-contents more)))
        ;; Return the articles in the right order.
        (setq nnweb-articles
-             (sort (nconc nnweb-articles map)
-                   (lambda (s1 s2) (< (car s1) (car s2)))))))))
+             (sort (nconc nnweb-articles map) 'car-less-than-car))))))
 
 (defun nnweb-dejanews-wash-article ()
   (let ((case-fold-search t))
       (replace-match "\\1 " t)
       (forward-line 1))
     (when (re-search-forward "\n\n+" nil t)
-      (replace-match "\n" t t))))
+      (replace-match "\n" t t))
+    (goto-char (point-min))
+    (when (search-forward "[More Headers]" nil t)
+      (replace-match "" t t))))
 
 (defun nnweb-dejanews-search (search)
   (nnweb-fetch-form
      ("defaultOp" . "AND")
      ("svcclass" . "dncurrent")
      ("maxhits" . "100")
-     ("format" . "verbose")
+     ("format" . "verbose2")
+     ("threaded" . "0")
+     ("showsort" . "date")
+     ("agesign" . "1")
+     ("ageweight" . "1")))
+  t)
+
+(defun nnweb-dejanewsold-search (search)
+  (nnweb-fetch-form
+   (nnweb-definition 'address)
+   `(("query" . ,search)
+     ("defaultOp" . "AND")
+     ("svcclass" . "dnold")
+     ("maxhits" . "100")
+     ("format" . "verbose2")
      ("threaded" . "0")
-     ("showsort" . "score")
+     ("showsort" . "date")
      ("agesign" . "1")
      ("ageweight" . "1")))
   t)
          (setq more nil))
        ;; Return the articles in the right order.
        (setq nnweb-articles
-             (sort (nconc nnweb-articles map)
-                   (lambda (s1 s2) (< (car s1) (car s2)))))))))
+             (sort (nconc nnweb-articles map) 'car-less-than-car))))))
 
 (defun nnweb-reference-wash-article ()
   (let ((case-fold-search t))
       (set-marker body nil))))
 
 (defun nnweb-reference-search (search)
-  (prog1
-      (url-insert-file-contents
-       (concat
-       (nnweb-definition 'address)
-       "?"
-       (nnweb-encode-www-form-urlencoded
-        `(("search" . "advanced")
-          ("querytext" . ,search)
-          ("subj" . "")
-          ("name" . "")
-          ("login" . "")
-          ("host" . "")
-          ("organization" . "")
-          ("groups" . "")
-          ("keywords" . "")
-          ("choice" . "Search")
-          ("startmonth" . "Jul")
-          ("startday" . "25")
-          ("startyear" . "1996")
-          ("endmonth" . "Aug")
-          ("endday" . "24")
-          ("endyear" . "1996")
-          ("mode" . "Quick")
-          ("verbosity" . "Verbose")
-          ("ranking" . "Relevance")
-          ("first" . "1")
-          ("last" . "25")
-          ("score" . "50")))))
-    (setq buffer-file-name nil))
+  (url-insert-file-contents
+   (concat
+    (nnweb-definition 'address)
+    "?"
+    (nnweb-encode-www-form-urlencoded
+     `(("search" . "advanced")
+       ("querytext" . ,search)
+       ("subj" . "")
+       ("name" . "")
+       ("login" . "")
+       ("host" . "")
+       ("organization" . "")
+       ("groups" . "")
+       ("keywords" . "")
+       ("choice" . "Search")
+       ("startmonth" . "Jul")
+       ("startday" . "25")
+       ("startyear" . "1996")
+       ("endmonth" . "Aug")
+       ("endday" . "24")
+       ("endyear" . "1996")
+       ("mode" . "Quick")
+       ("verbosity" . "Verbose")
+       ("ranking" . "Relevance")
+       ("first" . "1")
+       ("last" . "25")
+       ("score" . "50")))))
+  (setq buffer-file-name nil)
   t)
 
 ;;;
              (setq more nil)))
          ;; Return the articles in the right order.
          (setq nnweb-articles
-               (sort (nconc nnweb-articles map)
-                     (lambda (s1 s2) (< (car s1) (car s2))))))))))
+               (sort (nconc nnweb-articles map) 'car-less-than-car)))))))
 
 (defun nnweb-altavista-wash-article ()
   (goto-char (point-min))
     (nnweb-remove-markup)))
 
 (defun nnweb-altavista-search (search &optional part)
-  (prog1
-      (url-insert-file-contents
-       (concat
-       (nnweb-definition 'address)
-       "?"
-       (nnweb-encode-www-form-urlencoded
-        `(("pg" . "aq")
-          ("what" . "news")
-          ,@(when part `(("stq" . ,(int-to-string (* part 30)))))
-          ("fmt" . "d")
-          ("q" . ,search)
-          ("r" . "")
-          ("d0" . "")
-          ("d1" . "")))))
-    (setq buffer-file-name nil)))
+  (url-insert-file-contents
+   (concat
+    (nnweb-definition 'address)
+    "?"
+    (nnweb-encode-www-form-urlencoded
+     `(("pg" . "aq")
+       ("what" . "news")
+       ,@(when part `(("stq" . ,(int-to-string (* part 30)))))
+       ("fmt" . "d")
+       ("q" . ,search)
+       ("r" . "")
+       ("d0" . "")
+       ("d1" . "")))))
+  (setq buffer-file-name nil)
+  t)
+
+;;;
+;;; General web/w3 interface utility functions
+;;;
+
+(defun nnweb-insert-html (parse)
+  "Insert HTML based on a w3 parse tree."
+  (if (stringp parse)
+      (insert parse)
+    (insert "<" (symbol-name (car parse)) " ")
+    (insert (mapconcat
+            (lambda (param)
+              (concat (symbol-name (car param)) "="
+                      (prin1-to-string
+                       (if (consp (cdr param))
+                           (cadr param)
+                         (cdr param)))))
+            (nth 1 parse)
+            " "))
+    (insert ">\n")
+    (mapcar 'nnweb-insert-html (nth 2 parse))
+    (insert "</" (symbol-name (car parse)) ">\n")))
+
+(defun nnweb-encode-www-form-urlencoded (pairs)
+  "Return PAIRS encoded for forms."
+  (mapconcat
+   (function
+    (lambda (data)
+      (concat (w3-form-encode-xwfu (car data)) "="
+             (w3-form-encode-xwfu (cdr data)))))
+   pairs "&"))
+
+(defun nnweb-fetch-form (url pairs)
+  "Fetch a form from URL with PAIRS as the data."
+  (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
+       (url-request-method "POST")
+       (url-request-extra-headers
+        '(("Content-type" . "application/x-www-form-urlencoded"))))
+    (url-insert-file-contents url)
+    (setq buffer-file-name nil))
+  t)
+
+(defun nnweb-decode-entities ()
+  "Decode all HTML entities."
+  (goto-char (point-min))
+  (while (re-search-forward "&\\([a-z]+\\);" nil t)
+    (replace-match (char-to-string (or (cdr (assq (intern (match-string 1))
+                                                 w3-html-entities))
+                                      ?#))
+                  t t)))
+
+(defun nnweb-remove-markup ()
+  "Remove all HTML markup, leaving just plain text."
+  (goto-char (point-min))
+  (while (search-forward "<!--" nil t)
+    (delete-region (match-beginning 0)
+                  (or (search-forward "-->" nil t)
+                      (point-max))))
+  (goto-char (point-min))
+  (while (re-search-forward "<[^>]+>" nil t)
+    (replace-match "" t t)))
+
+(defun nnweb-insert (url)
+  "Insert the contents from an URL in the current buffer."
+  (let ((name buffer-file-name))
+    (url-insert-file-contents url)
+    (setq buffer-file-name name)))
+
+(defun nnweb-parse-find (type parse &optional maxdepth)
+  "Find the element of TYPE in PARSE."
+  (catch 'found
+    (nnweb-parse-find-1 type parse maxdepth)))
+
+(defun nnweb-parse-find-1 (type contents maxdepth)
+  (when (or (null maxdepth)
+           (not (zerop maxdepth)))
+    (when (consp contents)
+      (when (eq (car contents) type)
+       (throw 'found contents))
+      (when (listp (cdr contents))
+       (dolist (element contents)
+         (when (consp element)
+           (nnweb-parse-find-1 type element
+                               (and maxdepth (1- maxdepth)))))))))
+
+(defvar nnweb-text)
+(defun nnweb-text (parse)
+  "Return a list of text contents in PARSE."
+  (let ((nnweb-text nil))
+    (nnweb-text-1 parse)
+    (nreverse nnweb-text)))
+
+(defun nnweb-text-1 (contents)
+  (dolist (element contents)
+    (if (stringp element)
+       (push element nnweb-text)
+      (when (and (consp element)
+                (listp (cdr element)))
+       (nnweb-text-1 element)))))
 
 (provide 'nnweb)