Importing Oort Gnus v0.03.
[elisp/gnus.git-] / lisp / nnweb.el
index 2ad8cb2..154d9f3 100644 (file)
@@ -1,5 +1,6 @@
 ;;; nnweb.el --- retrieving articles via web search engines
-;; Copyright (C) 1996,97,98,99 Free Software Foundation, Inc.
+;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001
+;;        Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
     (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)))
+(unless noninteractive
+  (eval '(progn
+          (require 'w3)
+          (require 'url)
+          (require 'w3-forms))))
 
 (nnoo-declare nnweb)
 
@@ -58,19 +61,28 @@ Valid types include `dejanews', `dejanewsold', `reference',
 and `altavista'.")
 
 (defvar nnweb-type-definition
-  '((dejanews
-     (article . ignore)
-     (id . "http://search.dejanews.com/msgid.xp?MID=%s&fmt=text")
-     (map . nnweb-dejanews-create-mapping)
-     (search . nnweb-dejanews-search)
-     (address . "http://www.deja.com/=dnc/qs.xp")
-     (identifier . nnweb-dejanews-identity))
-    (dejanewsold
-     (article . ignore)
-     (map . nnweb-dejanews-create-mapping)
-     (search . nnweb-dejanewsold-search)
-     (address . "http://www.deja.com/dnquery.xp")
-     (identifier . nnweb-dejanews-identity))
+  '(
+    (dejanews ;; bought by google.com
+     (article . nnweb-google-wash-article)
+     (id . "http://groups.google.com/groups?as_umsgid=%s")
+     (reference . nnweb-google-reference)
+     (map . nnweb-google-create-mapping)
+     (search . nnweb-google-search)
+     (address . "http://groups.google.com/groups")
+     (identifier . nnweb-google-identity))
+;;;     (dejanews
+;;;      (article . ignore)
+;;;      (id . "http://search.dejanews.com/msgid.xp?MID=%s&fmt=text")
+;;;      (map . nnweb-dejanews-create-mapping)
+;;;      (search . nnweb-dejanews-search)
+;;;      (address . "http://www.deja.com/=dnc/qs.xp")
+;;;      (identifier . nnweb-dejanews-identity))
+;;;     (dejanewsold
+;;;      (article . ignore)
+;;;      (map . nnweb-dejanews-create-mapping)
+;;;      (search . nnweb-dejanewsold-search)
+;;;      (address . "http://www.deja.com/dnquery.xp")
+;;;      (identifier . nnweb-dejanews-identity))
     (reference
      (article . nnweb-reference-wash-article)
      (map . nnweb-reference-create-mapping)
@@ -121,7 +133,6 @@ and `altavista'.")
 
 (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)
@@ -132,15 +143,14 @@ and `altavista'.")
   (when (and group
             (not (equal group nnweb-group))
             (not nnweb-ephemeral-p))
+    (setq nnweb-group group
+         nnweb-articles nil)
     (let ((info (assoc group nnweb-group-alist)))
       (when info
-       (setq nnweb-group group)
        (setq nnweb-type (nth 2 info))
        (setq nnweb-search (nth 3 info))
        (unless dont-check
          (nnweb-read-overview group)))))
-  (unless dont-check
-    (nnweb-request-scan group))
   (cond
    ((not nnweb-articles)
     (nnheader-report 'nnweb "No matching articles"))
@@ -175,14 +185,17 @@ and `altavista'.")
                (and (stringp article)
                     (nnweb-definition 'id t)
                     (let ((fetch (nnweb-definition 'id))
-                          art)
+                          art active)
                       (when (string-match "^<\\(.*\\)>$" article)
                         (setq art (match-string 1 article)))
-                      (and fetch
-                           art
-                           (mm-with-unibyte-current-buffer
-                             (nnweb-fetch-url
-                              (format fetch article)))))))
+                      (when (and fetch art)
+                        (setq url (format fetch article))
+                        (mm-with-unibyte-current-buffer
+                          (nnweb-fetch-url url))
+                        (if (nnweb-definition 'reference t)
+                            (setq article
+                                  (funcall (nnweb-definition 
+                                            'reference) article)))))))
        (unless nnheader-callback-function
          (funcall (nnweb-definition 'article))
          (nnweb-decode-entities))
@@ -293,6 +306,7 @@ and `altavista'.")
   (when group
     (when (and (not nnweb-ephemeral-p)
               (not (equal group nnweb-group)))
+      (setq nnweb-hashtb (gnus-make-hashtable 4095))
       (nnweb-request-group group nil t))))
 
 (defun nnweb-init (server)
@@ -349,9 +363,13 @@ and `altavista'.")
       (setq url-current-callback-data data
            url-be-asynchronous t
            url-current-callback-func callback)
-      (url-retrieve url))
+      (url-retrieve url nil))
     (setq-default url-be-asynchronous old-asynch)))
 
+(if (fboundp 'url-retrieve-synchronously)
+    (defun nnweb-url-retrieve-asynch (url callback &rest data)
+      (url-retrieve url callback data)))
+
 ;;;
 ;;; DejaNews functions.
 ;;;
@@ -388,11 +406,13 @@ and `altavista'.")
                                     (car (rassq (string-to-number
                                                  (match-string 2 date))
                                                 parse-time-months))
-                                    (match-string 3 date) 
+                                    (match-string 3 date)
                                     (match-string 1 date)))
                (setq date "Jan 1 00:00:00 0000"))
              (incf i)
              (setq url (concat url "&fmt=text"))
+             (when (string-match "&context=[^&]+" url)
+               (setq url (replace-match "" t t url)))
              (unless (nnweb-get-hashtb url)
                (push
                 (list
@@ -676,13 +696,145 @@ and `altavista'.")
   t)
 
 ;;;
+;;; Deja bought by google.com
+;;;
+
+(defun nnweb-google-wash-article ()
+  (let ((case-fold-search t) url)
+    (goto-char (point-min))
+    (re-search-forward "^<pre>" nil t)
+    (narrow-to-region (point-min) (point))
+    (search-backward "</table>" nil t 2)
+    (delete-region (point-min) (point))
+    (if (search-forward "[view thread]" nil t)
+       (replace-match ""))
+    (goto-char (point-min))
+    (while (search-forward "<br>" nil t)
+      (replace-match "\n"))
+    (nnweb-remove-markup)
+    (goto-char (point-min))
+    (while (re-search-forward "^[ \t]*\n" nil t)
+      (replace-match ""))
+    (goto-char (point-max))
+    (insert "\n")
+    (widen)
+    (narrow-to-region (point) (point-max))
+    (search-forward "</pre>" nil t)
+    (delete-region (point) (point-max))
+    (nnweb-remove-markup)
+    (widen)))
+
+(defun nnweb-google-parse-1 (&optional Message-ID)
+  (let ((i 0)
+       (case-fold-search t)
+       (active (cadr (assoc nnweb-group nnweb-group-alist)))
+       Subject Score Date Newsgroups From
+       map url)
+    (unless active
+      (push (list nnweb-group (setq active (cons 1 0)) 
+                 nnweb-type nnweb-search)
+           nnweb-group-alist))
+    ;; Go through all the article hits on this page.
+    (goto-char (point-min))
+    (while (re-search-forward
+           "a href=/groups\\(\\?[^ \">]*seld=[0-9]+[^ \">]*\\)" nil t)
+      (setq url
+           (concat (nnweb-definition 'address)
+                   (match-string 1)))
+      (narrow-to-region (search-forward ">" nil t)
+                       (search-forward "</a>" nil t))
+      (nnweb-remove-markup)
+      (nnweb-decode-entities)
+      (setq Subject (buffer-string))
+      (goto-char (point-max))
+      (widen)
+      (forward-line 2)
+      (when (looking-at "<br><font[^>]+>")
+       (goto-char (match-end 0)))
+      (if (not (looking-at "<a[^>]+>"))
+         (skip-chars-forward " \t")
+       (narrow-to-region (point)
+                         (search-forward "</a>" nil t))
+       (nnweb-remove-markup)
+       (nnweb-decode-entities)
+       (setq Newsgroups (buffer-string))
+       (goto-char (point-max))
+       (widen)
+       (skip-chars-forward "- \t"))
+      (when (looking-at 
+            "\\([0-9]+/[A-Za-z]+/[0-9]+\\)[ \t]*by[ \t]*\\([^<]*\\) - <a")
+       (setq From (match-string 2)
+             Date (match-string 1)))
+      (forward-line 1)
+      (incf i)
+      (unless (nnweb-get-hashtb url)
+       (push
+        (list
+         (incf (cdr active))
+         (make-full-mail-header
+          (cdr active) (if Newsgroups
+                           (concat  "(" Newsgroups ") " Subject) 
+                         Subject)
+          From Date Message-ID
+          nil 0 0 url))
+        map)
+       (nnweb-set-hashtb (cadar map) (car map))))
+    map))
+
+(defun nnweb-google-reference (id)
+  (let ((map (nnweb-google-parse-1 id)) header)
+    (setq nnweb-articles 
+         (nconc nnweb-articles map))
+    (when (setq header (cadar map))
+      (mm-with-unibyte-current-buffer
+       (nnweb-fetch-url (mail-header-xref header)))
+      (caar map))))
+
+(defun nnweb-google-create-mapping ()
+  "Perform the search and create an number-to-url alist."
+  (save-excursion
+    (set-buffer nnweb-buffer)
+    (erase-buffer)
+    (when (funcall (nnweb-definition 'search) nnweb-search)
+       (let ((more t))
+         (while more
+           (setq nnweb-articles
+                 (nconc nnweb-articles (nnweb-google-parse-1)))
+           ;; FIXME: There is more.
+           (setq more nil))
+         ;; Return the articles in the right order.
+         (setq nnweb-articles
+               (sort nnweb-articles 'car-less-than-car))))))
+
+(defun nnweb-google-search (search)
+  (nnweb-insert
+   (concat
+    (nnweb-definition 'address)
+    "?"
+    (nnweb-encode-www-form-urlencoded
+     `(("q" . ,search)
+       ("num". "100")
+       ("hq" . "")
+       ("hl" . "")
+       ("lr" . "")
+       ("safe" . "off")
+       ("sites" . "groups")))))
+  t)
+
+(defun nnweb-google-identity (url)
+  "Return an unique identifier based on URL."
+  (if (string-match "seld=\\([0-9]+\\)" url)
+      (match-string 1 url)
+    url))
+
+;;;
 ;;; 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 (nnheader-string-as-multibyte parse))
     (insert "<" (symbol-name (car parse)) " ")
     (insert (mapconcat
             (lambda (param)
@@ -720,20 +872,21 @@ and `altavista'.")
   "Decode all HTML entities."
   (goto-char (point-min))
   (while (re-search-forward "&\\(#[0-9]+\\|[a-z]+\\);" nil t)
-    (replace-match (char-to-string 
-                   (if (eq (aref (match-string 1) 0) ?\#)
+    (let ((elem (if (eq (aref (match-string 1) 0) ?\#)
                        (let ((c
-                              (string-to-number (substring 
+                              (string-to-number (substring
                                                  (match-string 1) 1))))
                          (if (mm-char-or-char-int-p c) c 32))
                      (or (cdr (assq (intern (match-string 1))
                                     w3-html-entities))
-                         ?#)))
-                  t t)))
+                         ?#))))
+      (unless (stringp elem)
+       (setq elem (char-to-string elem)))
+      (replace-match elem t t))))
 
-(defun nnweb-decode-entities-string (str)
+(defun nnweb-decode-entities-string (string)
   (with-temp-buffer
-    (insert str)
+    (insert string)
     (nnweb-decode-entities)
     (buffer-substring (point-min) (point-max))))
 
@@ -757,8 +910,8 @@ If FOLLOW-REFRESH is non-nil, redirect refresh url in META."
          (narrow-to-region (point) (point))
          (url-insert-file-contents url)
          (goto-char (point-min))
-         (when (re-search-forward 
-                "HTTP-EQUIV=\"Refresh\"[^>]*URL=\\([^\"]+\\)\"" nil t)
+         (when (re-search-forward
+                "<meta[ \t\r\n]*http-equiv=\"Refresh\"[^>]*URL=\\([^\"]+\\)\"" nil t)
            (let ((url (match-string 1)))
              (delete-region (point-min) (point-max))
              (nnweb-insert url t))))
@@ -814,6 +967,11 @@ If FOLLOW-REFRESH is non-nil, redirect refresh url in META."
                 (listp (cdr element)))
        (nnweb-text-1 element)))))
 
+(defun nnweb-replace-in-string (string match newtext)
+  (while (string-match match string)
+    (setq string (replace-match newtext t t string)))
+  string)
+
 (provide 'nnweb)
 
 ;;; nnweb.el ends here