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