-;; TODO: spam scores, detection of spam in newsgroups, cross-server splitting, remote processing, training through files
-
;;; spam.el --- Identifying spam
-;; Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: network
;;; Several TODO items are marked as such
+;; TODO: spam scores, detection of spam in newsgroups, cross-server splitting,
+;; remote processing, training through files
+
;;; Code:
(eval-when-compile (require 'cl))
(require 'gnus-uu) ; because of key prefix issues
;;; for the definitions of group content classification and spam processors
-(require 'gnus)
+(require 'gnus)
(require 'message) ;for the message-fetch-field functions
;; for nnimap-split-download-body-default
(eval-when-compile (require 'nnimap))
-;; autoload executable-find
-(eval-and-compile
- ;; executable-find is not autoloaded in Emacs 20
- (autoload 'executable-find "executable"))
-
;; autoload query-dig
(eval-and-compile
(autoload 'query-dig "dig"))
;; autoload gnus-registry
(eval-and-compile
+ (autoload 'gnus-registry-group-count "gnus-registry")
+ (autoload 'gnus-registry-add-group "gnus-registry")
(autoload 'gnus-registry-store-extra-entry "gnus-registry")
(autoload 'gnus-registry-fetch-extra "gnus-registry"))
(defgroup spam nil
"Spam configuration.")
-(defcustom spam-directory "~/News/spam/"
+(defcustom spam-directory (nnheader-concat gnus-directory "spam/")
"Directory for spam whitelists and blacklists."
:type 'directory
:group 'spam)
:type 'boolean
:group 'spam)
+(defcustom spam-split-symbolic-return nil
+ "Whether `spam-split' should work with symbols or group names."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-split-symbolic-return-positive nil
+ "Whether `spam-split' should ALWAYS work with symbols or group names.
+Do not set this if you use `spam-split' in a fancy split
+ method."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-process-ham-in-spam-groups nil
"Whether ham should be processed in spam groups."
:type 'boolean
:group 'spam)
(defcustom spam-mark-only-unseen-as-spam t
- "Whether only unseen articles should be marked as spam in spam
-groups. When nil, all unread articles in a spam group are marked as
+ "Whether only unseen articles should be marked as spam in spam groups.
+When nil, all unread articles in a spam group are marked as
spam. Set this if you want to leave an article unread in a spam group
without losing it to the automatic spam-marking process."
:type 'boolean
:group 'spam)
(defcustom spam-mark-ham-unread-before-move-from-spam-group nil
- "Whether ham should be marked unread before it's moved out of a spam
-group according to ham-process-destination. This variable is an
-official entry in the international Longest Variable Name
+ "Whether ham should be marked unread before it's moved.
+The article is moved out of a spam group according to ham-process-destination.
+This variable is an official entry in the international Longest Variable Name
Competition."
:type 'boolean
:group 'spam)
+(defcustom spam-disable-spam-split-during-ham-respool nil
+ "Whether `spam-split' should be ignored while resplitting ham.
+This is useful to prevent ham from ending up in the same spam
+group after the resplit. Don't set this to t if you have `spam-split' as the
+last rule in your split configuration."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-autodetect-recheck-messages nil
+ "Should spam.el recheck all meessages when autodetecting?
+Normally this is nil, so only unseen messages will be checked."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-whitelist (expand-file-name "whitelist" spam-directory)
"The location of the whitelist.
The file format is one regular expression per line.
:group 'spam)
(defcustom spam-use-dig t
- "Whether query-dig should be used instead of query-dns."
+ "Whether `query-dig' should be used instead of `query-dns'."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-use-gmane-xref nil
+ "Whether the Gmane spam xref should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-blacklist nil
- "Whether the blacklist should be used by spam-split."
+ "Whether the blacklist should be used by `spam-split'."
:type 'boolean
:group 'spam)
:group 'spam)
(defcustom spam-use-whitelist nil
- "Whether the whitelist should be used by spam-split."
+ "Whether the whitelist should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-whitelist-exclusive nil
- "Whether whitelist-exclusive should be used by spam-split.
+ "Whether whitelist-exclusive should be used by `spam-split'.
Exclusive whitelisting means that all messages from senders not in the whitelist
are considered spam."
:type 'boolean
:group 'spam)
(defcustom spam-use-blackholes nil
- "Whether blackholes should be used by spam-split."
+ "Whether blackholes should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-hashcash nil
- "Whether hashcash payments should be detected by spam-split."
+ "Whether hashcash payments should be detected by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-regex-headers nil
- "Whether a header regular expression match should be used by spam-split.
+ "Whether a header regular expression match should be used by `spam-split'.
Also see the variables `spam-regex-headers-spam' and `spam-regex-headers-ham'."
:type 'boolean
:group 'spam)
(defcustom spam-use-regex-body nil
- "Whether a body regular expression match should be used by spam-split.
+ "Whether a body regular expression match should be used by `spam-split'.
Also see the variables `spam-regex-body-spam' and `spam-regex-body-ham'."
:type 'boolean
:group 'spam)
(defcustom spam-use-bogofilter-headers nil
- "Whether bogofilter headers should be used by spam-split.
+ "Whether bogofilter headers should be used by `spam-split'.
Enable this if you pre-process messages with Bogofilter BEFORE Gnus sees them."
:type 'boolean
:group 'spam)
(defcustom spam-use-bogofilter nil
- "Whether bogofilter should be invoked by spam-split.
+ "Whether bogofilter should be invoked by `spam-split'.
Enable this if you want Gnus to invoke Bogofilter on new messages."
:type 'boolean
:group 'spam)
+(defcustom spam-use-bsfilter-headers nil
+ "Whether bsfilter headers should be used by `spam-split'.
+Enable this if you pre-process messages with Bsfilter BEFORE Gnus sees them."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-use-bsfilter nil
+ "Whether bsfilter should be invoked by `spam-split'.
+Enable this if you want Gnus to invoke Bsfilter on new messages."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-use-BBDB nil
- "Whether BBDB should be used by spam-split."
+ "Whether BBDB should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-BBDB-exclusive nil
- "Whether BBDB-exclusive should be used by spam-split.
-Exclusive BBDB means that all messages from senders not in the BBDB are
+ "Whether BBDB-exclusive should be used by `spam-split'.
+Exclusive BBDB means that all messages from senders not in the BBDB are
considered spam."
:type 'boolean
:group 'spam)
(defcustom spam-use-ifile nil
- "Whether ifile should be used by spam-split."
+ "Whether ifile should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-stat nil
- "Whether spam-stat should be used by spam-split."
+ "Whether `spam-stat' should be used by `spam-split'."
:type 'boolean
:group 'spam)
(defcustom spam-use-spamoracle nil
- "Whether spamoracle should be used by spam-split."
+ "Whether spamoracle should be used by `spam-split'."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-use-spamassassin nil
+ "Whether spamassassin should be invoked by `spam-split'.
+Enable this if you want Gnus to invoke SpamAssassin on new messages."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-use-spamassassin-headers nil
+ "Whether spamassassin headers should be checked by `spam-split'.
+Enable this if you pre-process messages with SpamAssassin BEFORE Gnus sees
+them."
:type 'boolean
:group 'spam)
(defcustom spam-install-hooks (or
spam-use-dig
+ spam-use-gmane-xref
spam-use-blacklist
- spam-use-whitelist
- spam-use-whitelist-exclusive
- spam-use-blackholes
- spam-use-hashcash
- spam-use-regex-headers
- spam-use-regex-body
- spam-use-bogofilter-headers
- spam-use-bogofilter
- spam-use-BBDB
- spam-use-BBDB-exclusive
- spam-use-ifile
+ spam-use-whitelist
+ spam-use-whitelist-exclusive
+ spam-use-blackholes
+ spam-use-hashcash
+ spam-use-regex-headers
+ spam-use-regex-body
+ spam-use-bogofilter
+ spam-use-bogofilter-headers
+ spam-use-spamassassin
+ spam-use-spamassassin-headers
+ spam-use-bsfilter
+ spam-use-bsfilter-headers
+ spam-use-BBDB
+ spam-use-BBDB-exclusive
+ spam-use-ifile
spam-use-stat
spam-use-spamoracle)
- "Whether the spam hooks should be installed, default to t if one of
-the spam-use-* variables is set."
+ "Whether the spam hooks should be installed.
+Default to t if one of the spam-use-* variables is set."
:group 'spam
:type 'boolean)
(defcustom spam-split-group "spam"
- "Group name where incoming spam should be put by spam-split."
+ "Group name where incoming spam should be put by `spam-split'."
:type 'string
:group 'spam)
;;; TODO: deprecate this variable, it's confusing since it's a list of strings,
;;; not regular expressions
-(defcustom spam-junk-mailgroups (cons
- spam-split-group
+(defcustom spam-junk-mailgroups (cons
+ spam-split-group
'("mail.junk" "poste.pourriel"))
"Mailgroups with spam contents.
All unmarked article in such group receive the spam mark on group entry."
:type '(repeat (string :tag "Group"))
:group 'spam)
-(defcustom spam-blackhole-servers '("bl.spamcop.net" "relays.ordb.org"
+
+(defcustom spam-gmane-xref-spam-group "gmane.spam.detected"
+ "The group where spam xrefs can be found on Gmane.
+Only meaningful if you enable `spam-use-gmane-xref'."
+ :type 'string
+ :group 'spam)
+
+(defcustom spam-blackhole-servers '("bl.spamcop.net" "relays.ordb.org"
"dev.null.dk" "relays.visi.com")
- "List of blackhole servers."
+ "List of blackhole servers.
+Only meaningful if you enable `spam-use-blackholes'."
:type '(repeat (string :tag "Server"))
:group 'spam)
(defcustom spam-blackhole-good-server-regex nil
- "String matching IP addresses that should not be checked in the blackholes"
+ "String matching IP addresses that should not be checked in the blackholes.
+Only meaningful if you enable `spam-use-blackholes'."
:type '(radio (const nil)
(regexp :format "%t: %v\n" :size 0))
:group 'spam)
(defcustom spam-face 'gnus-splash-face
- "Face for spam-marked articles"
+ "Face for spam-marked articles."
:type 'face
:group 'spam)
(defcustom spam-regex-headers-spam '("^X-Spam-Flag: YES")
- "Regular expression for positive header spam matches"
+ "Regular expression for positive header spam matches.
+Only meaningful if you enable `spam-use-regex-headers'."
:type '(repeat (regexp :tag "Regular expression to match spam header"))
:group 'spam)
(defcustom spam-regex-headers-ham '("^X-Spam-Flag: NO")
- "Regular expression for positive header ham matches"
+ "Regular expression for positive header ham matches.
+Only meaningful if you enable `spam-use-regex-headers'."
:type '(repeat (regexp :tag "Regular expression to match ham header"))
:group 'spam)
(defcustom spam-regex-body-spam '()
- "Regular expression for positive body spam matches"
+ "Regular expression for positive body spam matches.
+Only meaningful if you enable `spam-use-regex-body'."
:type '(repeat (regexp :tag "Regular expression to match spam body"))
:group 'spam)
(defcustom spam-regex-body-ham '()
- "Regular expression for positive body ham matches"
+ "Regular expression for positive body ham matches.
+Only meaningful if you enable `spam-use-regex-body'."
:type '(repeat (regexp :tag "Regular expression to match ham body"))
:group 'spam)
:group 'spam-ifile)
(defcustom spam-ifile-spam-category "spam"
- "Name of the spam ifile category."
+ "Name of the spam ifile category."
:type 'string
:group 'spam-ifile)
(defcustom spam-ifile-ham-category nil
- "Name of the ham ifile category. If nil, the current group name will
-be used."
+ "Name of the ham ifile category.
+If nil, the current group name will be used."
:type '(choice (string :tag "Use a fixed category")
(const :tag "Use the current group name"))
:group 'spam-ifile)
(defcustom spam-ifile-all-categories nil
"Whether the ifile check will return all categories, or just spam.
-Set this to t if you want to use the spam-split invocation of ifile as
+Set this to t if you want to use the `spam-split' invocation of ifile as
your main source of newsgroup names."
:type 'boolean
:group 'spam-ifile)
(defcustom spam-bogofilter-database-directory nil
"Directory path of the Bogofilter databases."
- :type '(choice (directory
+ :type '(choice (directory
:tag "Location of the Bogofilter database directory")
(const :tag "Use the default"))
:group 'spam-bogofilter)
+(defgroup spam-bsfilter nil
+ "Spam bsfilter configuration."
+ :group 'spam)
+
+(defcustom spam-bsfilter-path (executable-find "bsfilter")
+ "File path of the Bsfilter executable program."
+ :type '(choice (file :tag "Location of bsfilter")
+ (const :tag "Bsfilter is not installed"))
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-header "X-Spam-Flag"
+ "The header inserted by Bsfilter to flag spam."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-probability-header "X-Spam-Probability"
+ "The header that Bsfilter inserts in messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-spam-switch "--add-spam"
+ "The switch that Bsfilter uses to register spam messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-ham-switch "--add-ham"
+ "The switch that Bsfilter uses to register ham messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-spam-strong-switch "--sub-spam"
+ "The switch that Bsfilter uses to unregister ham messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-ham-strong-switch "--sub-clean"
+ "The switch that Bsfilter uses to unregister spam messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-database-directory nil
+ "Directory path of the Bsfilter databases."
+ :type '(choice (directory
+ :tag "Location of the Bsfilter database directory")
+ (const :tag "Use the default"))
+ :group 'spam-bsfilter)
+
(defgroup spam-spamoracle nil
"Spam spamoracle configuration."
:group 'spam)
-(defcustom spam-spamoracle-database nil
- "Location of spamoracle database file. When nil, use the default
-spamoracle database."
+(defcustom spam-spamoracle-database nil
+ "Location of spamoracle database file.
+When nil, use the default spamoracle database."
:type '(choice (directory :tag "Location of spamoracle database file.")
(const :tag "Use the default"))
:group 'spam-spamoracle)
(const :tag "Use the default"))
:group 'spam-spamoracle)
+(defgroup spam-spamassassin nil
+ "Spam SpamAssassin configuration."
+ :group 'spam)
+
+(defcustom spam-spamassassin-path (executable-find "spamassassin")
+ "File path of the spamassassin executable program.
+Hint: set this to \"spamc\" if you have spamd running. See the spamc and
+spamd man pages for more information on these programs."
+ :type '(choice (file :tag "Location of spamc")
+ (const :tag "spamassassin is not installed"))
+ :group 'spam-spamassassin)
+
+(defcustom spam-spamassassin-arguments ()
+ "Arguments to pass to the spamassassin executable.
+This must be a list. For example, `(\"-C\" \"configfile\")'."
+ :type '(restricted-sexp :match-alternatives (listp))
+ :group 'spam-spamassassin)
+
+(defcustom spam-spamassassin-spam-flag-header "X-Spam-Flag"
+ "The header inserted by SpamAssassin to flag spam."
+ :type 'string
+ :group 'spam-spamassassin)
+
+(defcustom spam-spamassassin-positive-spam-flag-header "YES"
+ "The regex on `spam-spamassassin-spam-flag-header' for positive spam
+identification"
+ :type 'string
+ :group 'spam-spamassassin)
+
+(defcustom spam-spamassassin-spam-status-header "X-Spam-Status"
+ "The header inserted by SpamAssassin, giving extended scoring information"
+ :type 'string
+ :group 'spam-spamassassin)
+
+(defcustom spam-sa-learn-path (executable-find "sa-learn")
+ "File path of the sa-learn executable program."
+ :type '(choice (file :tag "Location of spamassassin")
+ (const :tag "spamassassin is not installed"))
+ :group 'spam-spamassassin)
+
+(defcustom spam-sa-learn-rebuild t
+ "Whether sa-learn should rebuild the database every time it is called
+Enable this if you want sa-learn to rebuild the database automatically. Doing
+this will slightly increase the running time of the spam registration process.
+If you choose not to do this, you will have to run \"sa-learn --rebuild\" in
+order for SpamAssassin to recognize the new registered spam."
+ :type 'boolean
+ :group 'spam-spamassassin)
+
+(defcustom spam-sa-learn-spam-switch "--spam"
+ "The switch that sa-learn uses to register spam messages"
+ :type 'string
+ :group 'spam-spamassassin)
+
+(defcustom spam-sa-learn-ham-switch "--ham"
+ "The switch that sa-learn uses to register ham messages"
+ :type 'string
+ :group 'spam-spamassassin)
+
+(defcustom spam-sa-learn-unregister-switch "--forget"
+ "The switch that sa-learn uses to unregister messages messages"
+ :type 'string
+ :group 'spam-spamassassin)
+
;;; Key bindings for spam control.
(gnus-define-keys gnus-summary-mode-map
- "St" spam-bogofilter-score
+ "St" spam-generic-score
"Sx" gnus-summary-mark-as-spam
- "Mst" spam-bogofilter-score
+ "Mst" spam-generic-score
"Msx" gnus-summary-mark-as-spam
"\M-d" gnus-summary-mark-as-spam)
+(defvar spam-cache-lookups t
+ "Whether spam.el will try to cache lookups using `spam-caches'.")
+
+(defvar spam-caches (make-hash-table
+ :size 10
+ :test 'equal)
+ "Cache of spam detection entries.")
+
(defvar spam-old-ham-articles nil
"List of old ham articles, generated when a group is entered.")
(defvar spam-old-spam-articles nil
"List of old spam articles, generated when a group is entered.")
+(defvar spam-split-disabled nil
+ "If non-nil, `spam-split' is disabled, and always returns nil.")
+
+(defvar spam-split-last-successful-check nil
+ "Internal variable.
+`spam-split' will set this to nil or a spam-use-XYZ check if it
+finds ham or spam.")
;; convenience functions
-(defun spam-xor (a b) ; logical exclusive or
+(defun spam-clear-cache (symbol)
+ "Clear the spam-caches entry for a check."
+ (remhash symbol spam-caches))
+
+(defun spam-xor (a b)
+ "Logical A xor B."
(and (or a b) (not (and a b))))
(defun spam-group-ham-mark-p (group mark &optional spam)
+ "Checks if MARK is considered a ham mark in GROUP."
(when (stringp group)
(let* ((marks (spam-group-ham-marks group spam))
- (marks (if (symbolp mark)
- marks
+ (marks (if (symbolp mark)
+ marks
(mapcar 'symbol-value marks))))
(memq mark marks))))
(defun spam-group-spam-mark-p (group mark)
+ "Checks if MARK is considered a spam mark in GROUP."
(spam-group-ham-mark-p group mark t))
(defun spam-group-ham-marks (group &optional spam)
+ "In GROUP, get all the ham marks."
(when (stringp group)
(let* ((marks (if spam
(gnus-parameter-spam-marks group)
marks)))
(defun spam-group-spam-marks (group)
+ "In GROUP, get all the spam marks."
(spam-group-ham-marks group t))
(defun spam-group-spam-contents-p (group)
+ "Is GROUP a spam group?"
(if (stringp group)
(or (member group spam-junk-mailgroups)
- (memq 'gnus-group-spam-classification-spam
+ (memq 'gnus-group-spam-classification-spam
(gnus-parameter-spam-contents group)))
nil))
-
+
(defun spam-group-ham-contents-p (group)
+ "Is GROUP a ham group?"
(if (stringp group)
- (memq 'gnus-group-spam-classification-ham
+ (memq 'gnus-group-spam-classification-ham
(gnus-parameter-spam-contents group))
nil))
(defvar spam-list-of-processors
'((gnus-group-spam-exit-processor-report-gmane spam spam-use-gmane)
(gnus-group-spam-exit-processor-bogofilter spam spam-use-bogofilter)
+ (gnus-group-spam-exit-processor-bsfilter spam spam-use-bsfilter)
(gnus-group-spam-exit-processor-blacklist spam spam-use-blacklist)
(gnus-group-spam-exit-processor-ifile spam spam-use-ifile)
(gnus-group-spam-exit-processor-stat spam spam-use-stat)
(gnus-group-spam-exit-processor-spamoracle spam spam-use-spamoracle)
+ (gnus-group-spam-exit-processor-spamassassin spam spam-use-spamassassin)
(gnus-group-ham-exit-processor-ifile ham spam-use-ifile)
(gnus-group-ham-exit-processor-bogofilter ham spam-use-bogofilter)
+ (gnus-group-ham-exit-processor-bsfilter ham spam-use-bsfilter)
(gnus-group-ham-exit-processor-stat ham spam-use-stat)
(gnus-group-ham-exit-processor-whitelist ham spam-use-whitelist)
(gnus-group-ham-exit-processor-BBDB ham spam-use-BBDB)
(gnus-group-ham-exit-processor-copy ham spam-use-ham-copy)
+ (gnus-group-ham-exit-processor-spamassassin ham spam-use-spamassassin)
(gnus-group-ham-exit-processor-spamoracle ham spam-use-spamoracle))
- "The spam-list-of-processors list contains pairs associating a
-ham/spam exit processor variable with a classification and a
-spam-use-* variable.")
+ "The `spam-list-of-processors' list.
+This list contains pairs associating a ham/spam exit processor
+variable with a classification and a spam-use-* variable.")
(defun spam-group-processor-p (group processor)
(if (and (stringp group)
(symbolp processor))
(or (member processor (nth 0 (gnus-parameter-spam-process group)))
- (spam-group-processor-multiple-p
- group
+ (spam-group-processor-multiple-p
+ group
(cdr-safe (assoc processor spam-list-of-processors))))
nil))
(defun spam-group-ham-processor-spamoracle-p (group)
(spam-group-processor-p group 'gnus-group-ham-exit-processor-spamoracle))
+(defun spam-report-articles-gmane (n)
+ "Report the current message as spam.
+Respects the process/prefix convention."
+ (interactive "P")
+ (dolist (article (gnus-summary-work-articles n))
+ (gnus-summary-remove-process-mark article)
+ (spam-report-gmane article)))
+
+(defun spam-necessary-extra-headers ()
+ "Return the extra headers spam.el thinks are necessary."
+ (let (list)
+ (when (or spam-use-spamassassin
+ spam-use-spamassassin-headers
+ spam-use-regex-headers)
+ (push 'X-Spam-Status list))
+ list))
+
+(defun spam-user-format-function-S (headers)
+ (when headers
+ (spam-summary-score headers)))
+
+(defun spam-article-sort-by-spam-status (h1 h2)
+ "Sort articles by score."
+ (let (result)
+ (dolist (header (spam-necessary-extra-headers))
+ (let ((s1 (spam-summary-score h1 header))
+ (s2 (spam-summary-score h2 header)))
+ (unless (= s1 s2)
+ (setq result (< s1 s2))
+ (return))))
+ result))
+
+(defun spam-extra-header-to-number (header headers)
+ "Transform an extra header to a number."
+ (if (gnus-extra-header header headers)
+ (cond
+ ((eq header 'X-Spam-Status)
+ (string-to-number (gnus-replace-in-string
+ (gnus-extra-header header headers)
+ ".*hits=" "")))
+ (t nil))
+ nil))
+
+(defun spam-summary-score (headers &optional specific-header)
+ "Score an article for the summary buffer, as fast as possible.
+With SPECIFIC-HEADER, returns only that header's score.
+Will not return a nil score."
+ (let (score)
+ (dolist (header
+ (if specific-header
+ (list specific-header)
+ (spam-necessary-extra-headers)))
+ (setq score
+ (spam-extra-header-to-number header headers))
+ (when score
+ (return)))
+ (or score 0)))
+
+(defun spam-generic-score (&optional recheck)
+ "Invoke whatever scoring method we can."
+ (interactive "P")
+ (cond
+ ((or spam-use-spamassassin spam-use-spamassassin-headers)
+ (spam-spamassassin-score recheck))
+ ((or spam-use-bsfilter spam-use-bsfilter-headers)
+ (spam-bsfilter-score recheck))
+ (t (spam-bogofilter-score recheck))))
+
;;; Summary entry and exit processing.
(defun spam-summary-prepare ()
- (setq spam-old-ham-articles
+ (setq spam-old-ham-articles
(spam-list-articles gnus-newsgroup-articles 'ham))
- (setq spam-old-spam-articles
+ (setq spam-old-spam-articles
(spam-list-articles gnus-newsgroup-articles 'spam))
(spam-mark-junk-as-spam-routine))
;; we have to iterate over the processors, or else we'll be too slow
(dolist (classification '(spam ham))
(let* ((old-articles (if (eq classification 'spam)
- spam-old-spam-articles
+ spam-old-spam-articles
spam-old-ham-articles))
- (new-articles (spam-list-articles
- gnus-newsgroup-articles
+ (new-articles (spam-list-articles
+ gnus-newsgroup-articles
classification))
- (changed-articles (gnus-set-difference old-articles new-articles)))
+ (changed-articles (spam-set-difference new-articles old-articles)))
;; now that we have the changed articles, we go through the processors
(dolist (processor-param spam-list-of-processors)
(let ((processor (nth 0 processor-param))
unregister-list)
(dolist (article changed-articles)
(let ((id (spam-fetch-field-message-id-fast article)))
- (when (spam-log-unregistration-needed-p
+ (when (spam-log-unregistration-needed-p
id 'process classification check)
(push article unregister-list))))
;; call spam-register-routine with specific articles to unregister,
;; when there are articles to unregister and the check is enabled
(when (and unregister-list (symbol-value check))
- (spam-register-routine classification check t unregister-list))))))
-
+ (spam-register-routine
+ classification check t unregister-list))))))
+
;; find all the spam processors applicable to this group
(dolist (processor-param spam-list-of-processors)
(let ((processor (nth 0 processor-param))
(spam-group-processor-p gnus-newsgroup-name processor))
(spam-register-routine classification check))))
- (if spam-move-spam-nonspam-groups-only
- (when (not (spam-group-spam-contents-p gnus-newsgroup-name))
- (spam-mark-spam-as-expired-and-move-routine
- (gnus-parameter-spam-process-destination gnus-newsgroup-name)))
- (gnus-message 5 "Marking spam as expired and moving it to %s"
- gnus-newsgroup-name)
- (spam-mark-spam-as-expired-and-move-routine
- (gnus-parameter-spam-process-destination gnus-newsgroup-name)))
+ (unless (and spam-move-spam-nonspam-groups-only
+ (spam-group-spam-contents-p gnus-newsgroup-name))
+ (when (< 0 (length (spam-list-articles
+ gnus-newsgroup-articles
+ 'spam)))
+ (gnus-message 6 "Marking spam as expired and moving it to %s"
+ (gnus-parameter-spam-process-destination
+ gnus-newsgroup-name))
+ (spam-mark-spam-as-expired-and-move-routine
+ (gnus-parameter-spam-process-destination gnus-newsgroup-name))))
;; now we redo spam-mark-spam-as-expired-and-move-routine to only
;; expire spam, in case the above did not expire them
- (gnus-message 5 "Marking spam as expired without moving it")
- (spam-mark-spam-as-expired-and-move-routine nil)
+ (when (< 0 (length (spam-list-articles
+ gnus-newsgroup-articles
+ 'spam)))
+ (gnus-message 6 "Marking spam as expired without moving it")
+ (spam-mark-spam-as-expired-and-move-routine nil))
(when (or (spam-group-ham-contents-p gnus-newsgroup-name)
(and (spam-group-spam-contents-p gnus-newsgroup-name)
(spam-group-processor-p gnus-newsgroup-name processor))
(spa