1;;; rmail-spam-filter.el  --- spam filter for rmail, the emacs mail reader.
2
3;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
4;; Keywords: email, spam, filter, rmail
5;; Author: Eli Tziperman <eli AT deas.harvard.edu>
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING.  If not, write to the
21;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22;; Boston, MA 02110-1301, USA.
23
24;;; Commentary:
25;;; -----------
26
27;;; Automatically recognize and delete junk email before it is
28;;; displayed in rmail/rmail-summary.  Spam emails are defined by
29;;; specifying one or more of the sender, subject and contents.
30;;; URL: http://www.weizmann.ac.il/~eli/Downloads/rmail-spam-filter/
31
32;;; Usage:
33;;; ------
34
35;;; put in your .emacs:
36
37;;; (load "rmail-spam-filter.el")
38
39;;; and use customize (in rmail-spam-filter group) to:
40
41;;; (*) turn on the variable rmail-use-spam-filter,
42
43;;; (*) specify in variable rsf-definitions-alist what sender,
44;;; subject and contents make an email be considered spam.
45
46;;; in addition, you may:
47
48;;; (*) Block future mail with the subject or sender of a message
49;;; while reading it in RMAIL: just click on the "Spam" item on the
50;;; menubar, and add the subject or sender to the list of spam
51;;; definitions using the mouse and the appropriate menu item. You
52;;; need to later also save the list of spam definitions using the
53;;; same menu item, or alternatively, see variable
54;;; `rsf-autosave-newly-added-definitions'.
55
56;;; (*) specify if blind-cc'ed mail (no "To:" header field) is to be
57;;; treated as spam (variable rsf-no-blind-cc; Thanks to Ethan
58;;; Brown <ethan@gso.saic.com> for this).
59
60;;; (*) specify if rmail-spam-filter should ignore case of spam
61;;; definitions (variable rsf-ignore-case; Thanks to
62;;; Ethan Brown <ethan@gso.saic.com> for the suggestion).
63
64;;; (*) Specify a "white-list" of trusted senders. If any
65;;; rsf-white-list string matches a substring of the "From"
66;;; header, the message is flagged as a valid, non-spam message (Ethan
67;;; Brown <ethan@gso.saic.com>).
68
69;;; (*) rmail-spam-filter is best used with a general purpose spam
70;;; filter such as the procmail-based http://www.spambouncer.org/.
71;;; Spambouncer is set to only mark messages as spam/blocked/bulk/OK
72;;; via special headers, and these headers may then be defined in
73;;; rmail-spam-filter such that the spam is rejected by
74;;; rmail-spam-filter itself.
75
76;;; (*) rmail spam filter also works with bbdb to prevent spam senders
77;;; from entering into the .bbdb file.  See variable
78;;; "rsf-auto-delete-spam-bbdb-entries".  This is done
79;;; in two ways: (a) bbdb is made not to auto-create entries for
80;;; messages that are deleted by the rmail-spam-filter, (b) when a
81;;; message is deleted in rmail, the user is offered to delete the
82;;; sender's bbdb entry as well _if_ it was created at the same day.
83
84(require 'rmail)
85(if (> emacs-major-version 20)
86    (require 'rmailsum)
87  (if (not (fboundp 'rmail-make-summary-line)) (load-library "rmailsum")))
88
89(defvar bbdb/mail_auto_create_p)
90(defvar rmail-summary-mode-map)
91
92;; For find-if and other cool common lisp functions we may want to use.
93(eval-when-compile
94  (require 'cl))
95
96(defgroup rmail-spam-filter nil
97  "Spam filter for RMAIL, the mail reader for Emacs."
98  :group 'rmail)
99
100(defcustom rmail-use-spam-filter nil
101  "*Non-nil to activate the rmail spam filter.
102Specify `rsf-definitions-alist' to define what you consider spam
103emails."
104  :type 'boolean
105  :group 'rmail-spam-filter )
106
107(defcustom rsf-file "~/XRMAIL-SPAM"
108  "*Name of rmail file for optionally saving some of the spam.
109Spam may be either just deleted, or saved in a separate spam file to
110be looked at at a later time.  Whether the spam is just deleted or
111also saved in a separete spam file is specified for each definition of
112spam, as one of the fields of `rsf-definitions-alist'"
113  :type 'string
114  :group 'rmail-spam-filter )
115
116(defcustom rsf-no-blind-cc nil
117  "*Non-nil to treat blind CC (no To: header) as spam."
118  :type 'boolean
119  :group 'rmail-spam-filter )
120
121(defcustom rsf-ignore-case nil
122  "*Non-nil to ignore case in `rsf-definitions-alist'."
123  :type 'boolean
124  :group 'rmail-spam-filter )
125
126(defcustom rsf-beep nil
127  "*Non-nil to beep if spam is found."
128  :type 'boolean
129  :group 'rmail-spam-filter )
130
131(defcustom rsf-sleep-after-message 2.0
132  "*Seconds to wait after display of message that spam was found."
133  :type 'number
134  :group 'rmail-spam-filter )
135
136(defcustom rsf-min-region-to-spam-list 7
137  "*Minimum size of region that you can add to the spam list.
138This is a size limit on text that you can specify as
139indicating a message is spam.  The aim is to avoid
140accidentally adding a too short region, which would result
141in false positive identification of spam."
142  :type 'integer
143  :group 'rmail-spam-filter )
144
145(defcustom rsf-auto-delete-spam-bbdb-entries nil
146  "*Non-nil to make sure no entries are made in bbdb for spam emails.
147This is done in two ways: (1) bbdb is made not to auto-create entries
148for messages that are deleted by the `rmail-spam-filter', (2) when a
149message is deleted in rmail, the user is offered to delete the
150sender's bbdb entry as well if it was created at the same day.  Note
151that Emacs needs to be restarted after setting this option for it to
152take an effect."
153  :type 'boolean
154  :group 'rmail-spam-filter )
155
156(defcustom rsf-autosave-newly-added-definitions nil
157  "*Non-nil to auto save new spam entries.
158New entries entered via the spam menu bar item are then saved to
159customization file immediately after being added via the menu bar, and
160do not require explicitly saving the file after adding the new
161entries."
162  :type 'boolean
163  :group 'rmail-spam-filter )
164
165(defcustom rsf-white-list nil
166  "*List of strings to identify valid senders.
167If any rsf-white-list string matches a substring of the 'From'
168header, the message is flagged as a valid, non-spam message.  Example:
169If your domain is emacs.com then including 'emacs.com' in your
170rsf-white-list would flag all mail from your colleagues as
171valid."
172  :type '(repeat string)
173  :group 'rmail-spam-filter )
174
175(defcustom rsf-definitions-alist nil
176  "*Alist matching strings defining what messages are considered spam.
177Each definition may contain specifications of one or more of the
178elements {subject, sender, recipients or contents}, as well as a
179definition of what to do with the spam (action item).  A spam e-mail
180is defined as one that fits all of the specified elements of any one
181of the spam definitions.  The strings that specify spam subject,
182sender, etc, may be regexp.  For example, to specify that the subject
183may be either 'this is spam' or 'another spam', use the regexp: 'this
184is spam\\|another spam' (without the single quotes).  To specify that
185if the contents contain both this and that the message is spam,
186specify 'this\\&that' in the appropriate spam definition field."
187  :type '(repeat
188          (list :format "%v"
189	   (cons :format "%v" :value (from . "")
190		 (const :format ""  from)
191		 (string :tag "From"  ""))
192	   (cons :format "%v" :value (to . "")
193		 (const :format ""  to)
194		 (string :tag "To"  ""))
195	   (cons :format "%v" :value (subject . "")
196		 (const :format ""  subject)
197		 (string :tag "Subject"  ""))
198	   (cons :format "%v" :value (content-type . "")
199		 (const :format ""  content-type)
200		 (string :tag "Content-Type"  ""))
201	   (cons :format "%v" :value (contents . "")
202		 (const :format ""  contents)
203		 (string :tag "Contents"  ""))
204	   (cons :format "%v" :value (action . output-and-delete)
205		 (const :format "" action)
206		 (choice :tag "Action selection"
207		  (const :tag "output to spam folder and delete" output-and-delete)
208		  (const :tag "delete spam" delete-spam)
209		  ))
210   ))
211  :group 'rmail-spam-filter)
212
213(defvar rsf-scanning-messages-now nil
214  "Non-nil when `rmail-spam-filter' scans messages.
215This is for interaction with `rsf-bbdb-auto-delete-spam-entries'.")
216
217;; the advantage over the automatic filter definitions is the AND conjunction
218;; of in-one-definition-elements
219(defun check-field (field-symbol message-data definition result)
220  "Check if field-symbol is in `rsf-definitions-alist'.
221Capture maybe-spam and this-is-a-spam-email in a cons in result,
222where maybe-spam is in first and this-is-a-spam-email is in rest.
223The values are returned by destructively changing result.
224If FIELD-SYMBOL field does not exist AND is not specified,
225this may still be spam due to another element...
226if (first result) is nil, we already have a contradiction in another
227field"
228  (let ((definition-field (cdr (assoc field-symbol definition))))
229    (if (and (first result) (> (length definition-field) 0))
230        ;; only in this case can maybe-spam change from t to nil
231        ;; ... else, if FIELD-SYMBOL field does appear in the message,
232        ;; and it also appears in spam definition list, this
233        ;; is potentially a spam:
234        (if (and message-data
235                 (string-match definition-field message-data))
236            ;; if we do not get a contradiction from another field, this is
237            ;; spam
238            (setf (rest result) t)
239          ;; the message data contradicts the specification, this is no spam
240          (setf (first result) nil)))))
241
242(defun rmail-spam-filter (msg)
243  "Return nil if msg is spam based on rsf-definitions-alist.
244If spam, optionally output msg to a file `rsf-file' and delete
245it from rmail file.  Called for each new message retrieved by
246`rmail-get-new-mail'."
247
248  (let ((old-message)
249	(return-value)
250	(this-is-a-spam-email)
251	(maybe-spam)
252	(message-sender)
253	(message-recipients)
254	(message-subject)
255	(message-content-type)
256	(num-spam-definition-elements)
257	(num-element 0)
258	(exit-while-loop nil)
259	(saved-case-fold-search case-fold-search)
260	(save-current-msg)
261	(rsf-saved-bbdb/mail_auto_create_p nil)
262	)
263
264    ;; make sure bbdb does not create entries for messages while spam
265    ;; filter is scanning the rmail file:
266    (setq rsf-saved-bbdb/mail_auto_create_p 'bbdb/mail_auto_create_p)
267    (setq bbdb/mail_auto_create_p nil)
268    ;; let `rsf-bbdb-auto-delete-spam-entries' know that rmail spam
269    ;; filter is running, so that deletion of rmail messages should be
270    ;; ignored for now:
271    (setq rsf-scanning-messages-now t)
272    (save-excursion
273      (save-restriction
274	(setq this-is-a-spam-email nil)
275	;; Narrow buffer to header of message and get Sender and
276	;; Subject fields to be used below:
277	(save-restriction
278	  (goto-char (rmail-msgbeg msg))
279	  (narrow-to-region (point) (progn (search-forward "\n\n") (point)))
280	  (setq message-sender (mail-fetch-field "From"))
281	  (setq message-recipients
282		(concat (mail-fetch-field "To")
283			(if (mail-fetch-field "Cc")
284			    (concat ", " (mail-fetch-field "Cc")))))
285	  (setq message-subject (mail-fetch-field "Subject"))
286	  (setq message-content-type (mail-fetch-field "Content-Type"))
287	  )
288	;; Find number of spam-definition elements in the list
289	;; rsf-definitions-alist specified by user:
290	(setq num-spam-definition-elements (safe-length
291					    rsf-definitions-alist))
292
293	;;; do we want to ignore case in spam definitions:
294	  (setq case-fold-search rsf-ignore-case)
295
296	;; Check for blind CC condition.  Set vars such that while
297	;; loop will be bypassed and spam condition will trigger
298	(if (and rsf-no-blind-cc
299		 (null message-recipients))
300	    (setq exit-while-loop t
301                  maybe-spam t
302                  this-is-a-spam-email t))
303
304        ;; Check white list, and likewise cause while loop
305        ;;  bypass.
306        (if (and message-sender
307                 (let ((white-list rsf-white-list)
308                       (found nil))
309                   (while (and (not found) white-list)
310                     (if (string-match (car white-list) message-sender)
311                         (setq found t)
312                       (setq white-list (cdr white-list))))
313                   found))
314            (setq exit-while-loop t
315                  maybe-spam nil
316                  this-is-a-spam-email nil))
317
318        ;; maybe-spam is in first, this-is-a-spam-email in rest, this
319        ;; simplifies the call to check-field
320        (setq maybe-spam (cons maybe-spam this-is-a-spam-email))
321
322	;; scan all elements of the list rsf-definitions-alist
323	(while (and
324		(< num-element num-spam-definition-elements)
325		(not exit-while-loop))
326          (let ((definition (nth num-element rsf-definitions-alist)))
327	    ;; Initialize maybe-spam which is set to t in one of two
328	    ;; cases: (1) unspecified definition-elements are found in
329	    ;; rsf-definitions-alist, (2) empty field is found
330	    ;; in the message being scanned (e.g. empty subject,
331	    ;; sender, recipients, etc).  The variable is set to nil
332	    ;; if a non empty field of the scanned message does not
333	    ;; match a specified field in
334	    ;; rsf-definitions-alist.
335
336	    ;; initialize this-is-a-spam-email to nil.  This variable
337	    ;; is set to t if one of the spam definitions matches a
338	    ;; field in the scanned message.
339            (setq maybe-spam (cons t nil))
340
341	    ;; start scanning incoming message:
342	    ;;---------------------------------
343
344            ;; Maybe the different fields should also be done in a
345            ;; loop to make the whole thing more flexible
346 	    ;; if sender field is not specified in message being
347	    ;; scanned, AND if "from" field does not appear in spam
348	    ;; definitions for this element, this may still be spam
349	    ;; due to another element...
350            (check-field 'from message-sender definition maybe-spam)
351 	    ;; next, if spam was not ruled out already, check recipients:
352            (check-field 'to message-recipients definition maybe-spam)
353 	    ;; next, if spam was not ruled out already, check subject:
354            (check-field 'subject message-subject definition maybe-spam)
355 	    ;; next, if spam was not ruled out already, check content-type:
356            (check-field 'content-type message-content-type
357                         definition maybe-spam)
358	    ;; next, if spam was not ruled out already, check
359	    ;; contents: if contents field is not specified, this may
360	    ;; still be spam due to another element...
361            (check-field 'contents
362                         (buffer-substring
363                          (rmail-msgbeg msg) (rmail-msgend msg))
364                         definition maybe-spam)
365
366	    ;; if the search in rsf-definitions-alist found
367	    ;; that this email is spam, output the email to the spam
368	    ;; rmail file, mark the email for deletion, leave the
369	    ;; while loop and return nil so that an rmail summary line
370	    ;; wont be displayed for this message:
371	    (if (and (first maybe-spam) (rest maybe-spam))
372		;; found that this is spam, no need to look at the
373		;; rest of the rsf-definitions-alist, exit
374		;; loop:
375		(setq exit-while-loop t)
376	      ;; else, spam was not yet found, increment number of
377	      ;; element in rsf-definitions-alist and proceed
378	      ;; to next element:
379	      (setq num-element (+ num-element 1)))
380	    )
381          )
382
383        ;; (BK) re-set originally used variables
384        (setq this-is-a-spam-email (rest maybe-spam)
385              maybe-spam (first maybe-spam))
386
387	(if (and this-is-a-spam-email maybe-spam)
388	    (progn
389	      ;;(message "Found spam!")
390	      ;;(ding 1) (sleep-for 2)
391
392	      ;; temprarily set rmail-current-message in order to
393	      ;; output and delete the spam msg if needed:
394	      (setq save-current-msg rmail-current-message)
395	      (setq rmail-current-message msg)
396	      ;; check action item and rsf-definitions-alist
397	      ;; and do it:
398	      (cond
399	       ((equal (cdr (assoc 'action
400				   (nth num-element rsf-definitions-alist)))
401		       'output-and-delete)
402		(progn
403		  (rmail-output-to-rmail-file rsf-file 1 t)
404                  ;; Don't delete if automatic deletion after output
405                  ;; is turned on
406		  (unless rmail-delete-after-output (rmail-delete-message))
407		  ))
408	       ((equal (cdr (assoc 'action
409				   (nth num-element rsf-definitions-alist)))
410		       'delete-spam)
411		(progn
412		  (rmail-delete-message)
413		  ))
414	       )
415	       (setq rmail-current-message save-current-msg)
416	       (setq bbdb/mail_auto_create_p
417	       'rsf-saved-bbdb/mail_auto_create_p)
418	      ;; set return value.  These lines must be last in the
419	      ;; function, so that they will determine the value
420	      ;; returned by rmail-spam-filter:
421	      (setq return-value nil))
422	    (setq return-value t))))
423    (setq case-fold-search saved-case-fold-search)
424    (setq rsf-scanning-messages-now nil)
425    return-value))
426
427
428;; define functions for interactively adding sender/subject of a
429;; specific message to the spam definitions while reading it, using
430;; the menubar:
431(defun rsf-add-subject-to-spam-list ()
432  (interactive)
433  (set-buffer rmail-buffer)
434  (let ((message-subject))
435    (setq message-subject (mail-fetch-field "Subject"))
436    ;; note the use of a backquote and comma on the subject line here,
437    ;; to make sure message-subject is actually evaluated and its value
438    ;; substituted:
439    (add-to-list 'rsf-definitions-alist
440		 (list '(from . "")
441		       '(to . "")
442		       `(subject . ,message-subject)
443		       '(content-type . "")
444		       '(contents . "")
445		       '(action . output-and-delete))
446		 t)
447    (customize-mark-to-save 'rsf-definitions-alist)
448    (if rsf-autosave-newly-added-definitions
449	(progn
450	  (custom-save-all)
451	  (message "%s" (concat "added subject \n <<< \n" message-subject
452			   " \n >>> \n to list of spam definitions. \n"
453			   "and saved the spam definitions to file.")))
454      (message "%s" (concat "added subject \n <<< \n" message-subject
455		       " \n >>> \n to list of spam definitions. \n"
456		       "Don't forget to save the spam definitions to file using the spam
457		       menu"))
458      )))
459
460(defun rsf-add-sender-to-spam-list ()
461  (interactive)
462  (set-buffer rmail-buffer)
463  (let ((message-sender))
464    (setq message-sender (mail-fetch-field "From"))
465    ;; note the use of a backquote and comma on the "from" line here,
466    ;; to make sure message-sender is actually evaluated and its value
467    ;; substituted:
468    (add-to-list 'rsf-definitions-alist
469		 (list `(from . ,message-sender)
470		       '(to . "")
471		       '(subject . "")
472		       '(content-type . "")
473		       '(contents . "")
474		       '(action . output-and-delete))
475		 t)
476    (customize-mark-to-save 'rsf-definitions-alist)
477    (if rsf-autosave-newly-added-definitions
478	(progn
479	  (custom-save-all)
480	  (message "%s" (concat "added sender \n <<< \n" message-sender
481			   " \n >>> \n to list of spam definitions. \n"
482			   "and saved the spam definitions to file.")))
483      (message "%s" (concat "added sender \n <<< \n " message-sender
484		       " \n >>> \n to list of spam definitions."
485		       "Don't forget to save the spam definitions to file using the spam
486		       menu"))
487      )))
488
489
490(defun rsf-add-region-to-spam-list ()
491  "Add the region makred by user in the rmail buffer to spam list.
492Added to spam definitions as a contents field."
493  (interactive)
494  (set-buffer rmail-buffer)
495  (let ((region-to-spam-list))
496    ;; check if region is inactive or has zero size:
497    (if (not (and mark-active (not (= (region-beginning) (region-end)))))
498	;; if inactive, print error message:
499	(message "you need to first highlight some text in the rmail buffer")
500      (if (< (- (region-end) (region-beginning)) rsf-min-region-to-spam-list)
501	  (message
502	   (concat "highlighted region is too small; min length set by variable \n"
503		   "rsf-min-region-to-spam-list"
504		   " is " (number-to-string rsf-min-region-to-spam-list)))
505	;; if region active and long enough, add to list of spam definisions:
506	(progn
507	  (setq region-to-spam-list (buffer-substring (region-beginning) (region-end)))
508	  ;; note the use of a backquote and comma on the "from" line here,
509	  ;; to make sure message-sender is actually evaluated and its value
510	  ;; substituted:
511	  (add-to-list 'rsf-definitions-alist
512		       (list '(from . "")
513			     '(to . "")
514			     '(subject . "")
515			     '(content-type . "")
516			     `(contents . ,region-to-spam-list)
517			     '(action . output-and-delete))
518		       t)
519	  (customize-mark-to-save 'rsf-definitions-alist)
520	  (if rsf-autosave-newly-added-definitions
521	      (progn
522		(custom-save-all)
523		(message "%s" (concat "added highlighted text \n <<< \n" region-to-spam-list
524				 " \n >>> \n to list of spam definitions. \n"
525				 "and saved the spam definitions to file.")))
526	    (message "%s" (concat "added highlighted text \n <<< \n " region-to-spam-list
527			     " \n >>> \n to list of spam definitions."
528			     "Don't forget to save the spam definitions to file using the
529			     spam menu"))
530	    ))))))
531
532
533(defun rsf-customize-spam-definitions ()
534  (interactive)
535  (customize-variable (quote rsf-definitions-alist)))
536
537(defun rsf-customize-group ()
538  (interactive)
539  (customize-group (quote rmail-spam-filter)))
540
541(defun rsf-custom-save-all ()
542  (interactive)
543  (custom-save-all))
544
545;; add the actual menu items and keyboard shortcuts to both rmail and
546;; rmail-summary menu-bars::
547(define-key rmail-summary-mode-map [menu-bar spam]
548  (cons "Spam" (make-sparse-keymap "Spam")))
549(define-key rmail-mode-map [menu-bar spam]
550  (cons "Spam" (make-sparse-keymap "Spam")))
551
552(define-key rmail-summary-mode-map [menu-bar spam customize-group]
553  '("Browse customizations of rmail spam filter" . rsf-customize-group))
554(define-key rmail-mode-map [menu-bar spam customize-group]
555  '("Browse customizations of rmail spam filter" . rsf-customize-group))
556(define-key rmail-summary-mode-map "\C-cSg" 'rsf-customize-group)
557(define-key rmail-mode-map "\C-cSg" 'rsf-customize-group)
558
559(define-key rmail-summary-mode-map [menu-bar spam customize-spam-list]
560  '("Customize list of spam definitions" . rsf-customize-spam-definitions))
561(define-key rmail-mode-map [menu-bar spam customize-spam-list]
562  '("Customize list of spam definitions" . rsf-customize-spam-definitions))
563(define-key rmail-summary-mode-map "\C-cSd" 'rsf-customize-spam-definitions)
564(define-key rmail-mode-map "\C-cSd" 'rsf-customize-spam-definitions)
565
566(define-key rmail-summary-mode-map [menu-bar spam lambda] '("----"))
567(define-key rmail-mode-map [menu-bar spam lambda] '("----"))
568
569(define-key rmail-summary-mode-map [menu-bar spam my-custom-save-all]
570  '("save newly added spam definitions to customization file" . rsf-custom-save-all))
571(define-key rmail-mode-map [menu-bar spam my-custom-save-all]
572  '("save newly added spam definitions to customization file" . rsf-custom-save-all))
573(define-key rmail-summary-mode-map "\C-cSa" 'rsf-custom-save-all)
574(define-key rmail-mode-map "\C-cSa" 'rsf-custom-save-all)
575
576(define-key rmail-summary-mode-map [menu-bar spam add-region-to-spam-list]
577  '("add region to spam list" . rsf-add-region-to-spam-list))
578(define-key rmail-mode-map [menu-bar spam add-region-to-spam-list]
579  '("add region to spam list" . rsf-add-region-to-spam-list))
580(define-key rmail-summary-mode-map "\C-cSn" 'rsf-add-region-to-spam-list)
581(define-key rmail-mode-map "\C-cSn" 'rsf-add-region-to-spam-list)
582
583(define-key rmail-summary-mode-map [menu-bar spam add-sender-to-spam-list]
584  '("add sender to spam list" . rsf-add-sender-to-spam-list))
585(define-key rmail-mode-map [menu-bar spam add-sender-to-spam-list]
586  '("add sender to spam list" . rsf-add-sender-to-spam-list))
587(define-key rmail-summary-mode-map "\C-cSr" 'rsf-add-sender-to-spam-list)
588(define-key rmail-mode-map "\C-cSr" 'rsf-add-sender-to-spam-list)
589
590(define-key rmail-summary-mode-map [menu-bar spam add-subject-to-spam-list]
591  '("add subject to spam list" . rsf-add-subject-to-spam-list))
592(define-key rmail-mode-map [menu-bar spam add-subject-to-spam-list]
593  '("add subject to spam list" . rsf-add-subject-to-spam-list))
594(define-key rmail-summary-mode-map "\C-cSt" 'rsf-add-subject-to-spam-list)
595(define-key rmail-mode-map "\C-cSt" 'rsf-add-subject-to-spam-list)
596
597(defun rsf-add-content-type-field ()
598  "Maintain backward compatibility for `rmail-spam-filter'.
599The most recent version of `rmail-spam-filter' checks the contents
600field of the incoming mail to see if it spam.  The format of
601`rsf-definitions-alist' has therefore changed.  This function
602checks to see if old format is used, and if it is, it converts
603`rsf-definitions-alist' to the new format.  Invoked
604automatically, no user input is required."
605  (interactive)
606  (if (and rsf-definitions-alist
607           (not (assoc 'content-type (car rsf-definitions-alist))))
608      (let ((result nil)
609            (current nil)
610            (definitions rsf-definitions-alist))
611        (while definitions
612          (setq current (car definitions))
613          (setq definitions (cdr definitions))
614          (setq result
615                (append result
616                        (list
617                         (list (assoc 'from current)
618                               (assoc 'to current)
619                               (assoc 'subject current)
620                               (cons 'content-type "")
621                               (assoc 'contents current)
622                               (assoc 'action current))))))
623        (setq rsf-definitions-alist result)
624        (customize-mark-to-save 'rsf-definitions-alist)
625        (if rsf-autosave-newly-added-definitions
626            (progn
627              (custom-save-all)
628              (message (concat "converted spam definitions to new format\n"
629                               "and saved the spam definitions to file.")))
630          (message (concat "converted spam definitions to new format\n"
631                           "Don't forget to save the spam definitions to file using the
632                           spam menu"))
633          ))))
634
635(provide 'rmail-spam-filter)
636
637;;; arch-tag: 03e1d45d-b72f-4dd7-8f04-e7fd78249746
638;;; rmail-spam-fitler ends here
639