comparison lisp/mail/rmail-spam-filter.el @ 102087:d6de2e3c19e8

(rmail-summary-mode-map): Remove unneeded declaration. (top-level): No need for cl now. (rmail-spam-filter, rmail-use-spam-filter, rsf-file, rsf-no-blind-cc) (rsf-beep, rsf-sleep-after-message, rsf-min-region-to-spam-list) (rsf-autosave-newly-added-definitions, rsf-white-list) (rsf-definitions-alist, rsf-check-field, rsf-add-subject-to-spam-list) (rsf-add-sender-to-spam-list, rsf-add-region-to-spam-list) (rsf-customize-spam-definitions, rsf-customize-group) (rsf-custom-save-all, rsf-add-content-type-field): Doc fixes. (rsf-check-field): Use setcar and setcdr rather than setf. (rmail-spam-filter): Simplify. (rsf-add-subject-to-spam-list, rsf-add-sender-to-spam-list) (rsf-add-region-to-spam-list): Use rmail-get-header or buffer-substring-no-properties. Regexp-quote the extracted data. Make the messages less verbose. (rmail-summary-mode-map, rmail-mode-map): Use easy-menu and dolist to simplify things. (rsf-add-content-type-field): Make the message less verbose.
author Glenn Morris <rgm@gnu.org>
date Wed, 18 Feb 2009 04:33:30 +0000
parents 5389a1f33e22
children 7053d753a548
comparison
equal deleted inserted replaced
102086:c70627aa5a6b 102087:d6de2e3c19e8
1 ;;; rmail-spam-filter.el --- spam filter for rmail, the emacs mail reader. 1 ;;; rmail-spam-filter.el --- spam filter for Rmail, the Emacs mail reader
2 2
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
4 ;; Free Software Foundation, Inc. 4 ;; Free Software Foundation, Inc.
5 ;; Keywords: email, spam, filter, rmail 5 ;; Keywords: email, spam, filter, rmail
6 ;; Author: Eli Tziperman <eli AT deas.harvard.edu> 6 ;; Author: Eli Tziperman <eli AT deas.harvard.edu>
73 ;;; rmail-spam-filter itself. 73 ;;; rmail-spam-filter itself.
74 74
75 (require 'rmail) 75 (require 'rmail)
76 (require 'rmailsum) 76 (require 'rmailsum)
77 77
78 (defvar rmail-summary-mode-map)
79
80 (eval-when-compile
81 (require 'cl)) ; for setf
82
83 (defgroup rmail-spam-filter nil 78 (defgroup rmail-spam-filter nil
84 "Spam filter for RMAIL, the mail reader for Emacs." 79 "Spam filter for Rmail, the Emacs mail reader."
85 :group 'rmail) 80 :group 'rmail)
86 81
87 (defcustom rmail-use-spam-filter nil 82 (defcustom rmail-use-spam-filter nil
88 "Non-nil to activate the rmail spam filter. 83 "Non-nil to activate the Rmail spam filter.
89 Specify `rsf-definitions-alist' to define what you consider spam 84 Set `rsf-definitions-alist' to define what you consider spam emails."
90 emails."
91 :type 'boolean 85 :type 'boolean
92 :group 'rmail-spam-filter ) 86 :group 'rmail-spam-filter)
93 87
94 (defcustom rsf-file "~/XRMAIL-SPAM" 88 (defcustom rsf-file "~/XRMAIL-SPAM"
95 "Name of rmail file for optionally saving some of the spam. 89 "Name of Rmail file for optionally saving some of the spam.
96 Spam may be either just deleted, or saved in a separate spam file to 90 You can either just delete spam, or save it in this file for
97 be looked at at a later time. Whether the spam is just deleted or 91 later review. Which action to take for each spam definition is
98 also saved in a separete spam file is specified for each definition of 92 specified by the \"action\" element of the definition."
99 spam, as one of the fields of `rsf-definitions-alist'"
100 :type 'string 93 :type 'string
101 :group 'rmail-spam-filter ) 94 :group 'rmail-spam-filter)
102 95
103 (defcustom rsf-no-blind-cc nil 96 (defcustom rsf-no-blind-cc nil
104 "Non-nil to treat blind CC (no To: header) as spam." 97 "Non-nil means mail with no explicit To: or Cc: is spam."
105 :type 'boolean 98 :type 'boolean
106 :group 'rmail-spam-filter ) 99 :group 'rmail-spam-filter)
107 100
108 (defcustom rsf-ignore-case nil 101 (defcustom rsf-ignore-case nil
109 "Non-nil to ignore case in `rsf-definitions-alist'." 102 "Non-nil means to ignore case in `rsf-definitions-alist'."
110 :type 'boolean 103 :type 'boolean
111 :group 'rmail-spam-filter ) 104 :group 'rmail-spam-filter)
112 105
113 (defcustom rsf-beep nil 106 (defcustom rsf-beep nil
114 "Non-nil to beep if spam is found." 107 "Non-nil means to beep if spam is found."
115 :type 'boolean 108 :type 'boolean
116 :group 'rmail-spam-filter ) 109 :group 'rmail-spam-filter)
117 110
118 (defcustom rsf-sleep-after-message 2.0 111 (defcustom rsf-sleep-after-message 2.0
119 "Seconds to wait after display of message that spam was found." 112 "Seconds to wait after displaying a message that spam was found."
120 :type 'number 113 :type 'number
121 :group 'rmail-spam-filter ) 114 :group 'rmail-spam-filter)
122 115
123 (defcustom rsf-min-region-to-spam-list 7 116 (defcustom rsf-min-region-to-spam-list 7
124 "Minimum size of region that you can add to the spam list. 117 "Minimum size of region that you can add to the spam list.
125 This is a size limit on text that you can specify as 118 The aim is to avoid adding too short a region, which could result
126 indicating a message is spam. The aim is to avoid 119 in false positive identification of a valid message as spam."
127 accidentally adding a too short region, which would result
128 in false positive identification of spam."
129 :type 'integer 120 :type 'integer
130 :group 'rmail-spam-filter ) 121 :group 'rmail-spam-filter)
131 122
132 (defcustom rsf-autosave-newly-added-definitions nil 123 (defcustom rsf-autosave-newly-added-definitions nil
133 "Non-nil to auto save new spam entries. 124 "Non-nil to auto-save new spam entries.
134 New entries entered via the spam menu bar item are then saved to 125 Any time you add an entry via the \"Spam\" menu, immediately saves
135 customization file immediately after being added via the menu bar, and 126 the custom file."
136 do not require explicitly saving the file after adding the new
137 entries."
138 :type 'boolean 127 :type 'boolean
139 :group 'rmail-spam-filter ) 128 :group 'rmail-spam-filter)
140 129
141 (defcustom rsf-white-list nil 130 (defcustom rsf-white-list nil
142 "List of strings to identify valid senders. 131 "List of regexps to identify valid senders.
143 If any rsf-white-list string matches a substring of the 'From' 132 If any element matches the \"From\" header, the message is
144 header, the message is flagged as a valid, non-spam message. Example: 133 flagged as a valid, non-spam message. E.g., if your domain is
145 If your domain is emacs.com then including 'emacs.com' in your 134 \"emacs.com\" then including \"emacs\\\\.com\" in this list would
146 rsf-white-list would flag all mail from your colleagues as 135 flag all mail (purporting to be) from your colleagues as valid."
147 valid."
148 :type '(repeat string) 136 :type '(repeat string)
149 :group 'rmail-spam-filter ) 137 :group 'rmail-spam-filter)
150 138
151 (defcustom rsf-definitions-alist nil 139 (defcustom rsf-definitions-alist nil
152 "Alist matching strings defining what messages are considered spam. 140 "A list of rules (definitions) matching spam messages.
153 Each definition may contain specifications of one or more of the 141 Each rule is an alist, with elements of the form (FIELD . REGEXP).
154 elements {subject, sender, recipients or contents}, as well as a 142 The recognized FIELDS are: from, to, subject, content-type,
155 definition of what to do with the spam (action item). A spam e-mail 143 x-spam-status, and contents. The \"contents\" element refers to
156 is defined as one that fits all of the specified elements of any one 144 the entire text of the message; all the other elements refer to
157 of the spam definitions. The strings that specify spam subject, 145 message headers of the same name.
158 sender, etc, may be regexp. For example, to specify that the subject 146
159 may be either 'this is spam' or 'another spam', use the regexp: 'this 147 Using an empty-string for REGEXP is the same as omitting that
160 is spam\\|another spam' (without the single quotes). To specify that 148 element altogether.
161 if the contents contain both this and that the message is spam, 149
162 specify 'this\\&that' in the appropriate spam definition field." 150 Each rule should contain one \"action\" element, saying what to do
151 if the rule is matched. This has the form (action . CHOICE), where
152 CHOICE may be either `output-and-delete' (save to `rsf-file', then delete),
153 or `delete-spam' (just delete).
154
155 A rule matches only if all the specified elements match."
163 :type '(repeat 156 :type '(repeat
164 (list :format "%v" 157 (list :format "%v"
165 (cons :format "%v" :value (from . "") 158 (cons :format "%v" :value (from . "")
166 (const :format "" from) 159 (const :format "" from)
167 (string :tag "From" "")) 160 (string :tag "From" ""))
181 (const :format "" x-spam-status) 174 (const :format "" x-spam-status)
182 (string :tag "X-Spam-Status" "")) 175 (string :tag "X-Spam-Status" ""))
183 (cons :format "%v" :value (action . output-and-delete) 176 (cons :format "%v" :value (action . output-and-delete)
184 (const :format "" action) 177 (const :format "" action)
185 (choice :tag "Action selection" 178 (choice :tag "Action selection"
186 (const :tag "output to spam folder and delete" output-and-delete) 179 (const :tag "Output and delete" output-and-delete)
187 (const :tag "delete spam" delete-spam) 180 (const :tag "Delete" delete-spam)
188 )) 181 ))))
189 )) 182 :group 'rmail-spam-filter)
190 :group 'rmail-spam-filter) 183
191 184 ;; FIXME nothing uses this, and it could just be let-bound.
192 ;; FIXME nothing uses this.
193 (defvar rsf-scanning-messages-now nil 185 (defvar rsf-scanning-messages-now nil
194 "Non-nil when `rmail-spam-filter' scans messages.") 186 "Non-nil when `rmail-spam-filter' scans messages.")
195 187
196 ;; the advantage over the automatic filter definitions is the AND conjunction 188 ;; the advantage over the automatic filter definitions is the AND conjunction
197 ;; of in-one-definition-elements 189 ;; of in-one-definition-elements
198 (defun rsf-check-field (field-symbol message-data definition result) 190 (defun rsf-check-field (field-symbol message-data definition result)
199 "Check if field-symbol is in `rsf-definitions-alist'. 191 "Check if a message appears to be spam.
200 Capture maybe-spam and this-is-a-spam-email in a cons in result, 192 FIELD-SYMBOL is one of the possible keys of a `rsf-definitions-alist'
201 where maybe-spam is in the car and this-is-a-spam-email is in the cdr. 193 rule; e.g. from, to. MESSAGE-DATA is a string giving the value of
202 The values are returned by destructively changing result. 194 FIELD-SYMBOL in the current message. DEFINITION is the element of
203 If FIELD-SYMBOL field does not exist AND is not specified, 195 `rsf-definitions-alist' currently being checked.
204 this may still be spam due to another element... 196
205 if (car result) is nil, we already have a contradiction in another 197 RESULT is a cons of the form (MAYBE-SPAM . IS-SPAM). If the car
206 field" 198 is nil, or if the entry for FIELD-SYMBOL in this DEFINITION is
199 absent or the empty string, this function does nothing.
200
201 Otherwise, if MESSAGE-DATA is non-nil and the entry matches it,
202 the cdr is set to t. Else, the car is set to nil."
207 (let ((definition-field (cdr (assoc field-symbol definition)))) 203 (let ((definition-field (cdr (assoc field-symbol definition))))
204 ;; Only in this case can maybe-spam change from t to nil.
208 (if (and (car result) (> (length definition-field) 0)) 205 (if (and (car result) (> (length definition-field) 0))
209 ;; only in this case can maybe-spam change from t to nil 206 ;; If FIELD-SYMBOL field appears in the message, and also in
210 ;; ... else, if FIELD-SYMBOL field does appear in the message, 207 ;; spam definition list, this is potentially a spam.
211 ;; and it also appears in spam definition list, this
212 ;; is potentially a spam:
213 (if (and message-data 208 (if (and message-data
214 (string-match definition-field message-data)) 209 (string-match definition-field message-data))
215 ;; if we do not get a contradiction from another field, this is 210 ;; If we do not get a contradiction from another field, this is spam
216 ;; spam 211 (setcdr result t)
217 (setf (cdr result) t) 212 ;; The message data contradicts the specification, this is not spam.
218 ;; the message data contradicts the specification, this is no spam 213 ;; Note that the total absence of a header specified in the
219 (setf (car result) nil))))) 214 ;; rule means this cannot be spam.
215 (setcar result nil)))))
220 216
221 (defun rmail-spam-filter (msg) 217 (defun rmail-spam-filter (msg)
222 "Return nil if msg is spam based on rsf-definitions-alist. 218 "Return nil if message number MSG is spam based on `rsf-definitions-alist'.
223 If spam, optionally output msg to a file `rsf-file' and delete 219 If spam, optionally output message to a file `rsf-file' and delete
224 it from rmail file. Called for each new message retrieved by 220 it from rmail file. Called for each new message retrieved by
225 `rmail-get-new-mail'." 221 `rmail-get-new-mail'."
226 222 (let ((return-value)
227 (let ((old-message) 223 ;; maybe-spam is in the car, this-is-a-spam-email in cdr.
228 (return-value) 224 (maybe-spam '(nil . nil))
229 (this-is-a-spam-email) 225 message-sender message-to message-cc message-recipients
230 (maybe-spam) 226 message-subject message-content-type message-spam-status
231 (message-sender) 227 (num-spam-definition-elements (safe-length rsf-definitions-alist))
232 (message-recipients)
233 (message-subject)
234 (message-content-type)
235 (message-spam-status)
236 (num-spam-definition-elements)
237 (num-element 0) 228 (num-element 0)
238 (exit-while-loop nil) 229 (exit-while-loop nil)
239 (saved-case-fold-search case-fold-search) 230 ;; Do we want to ignore case in spam definitions.
240 (save-current-msg) 231 (case-fold-search rsf-ignore-case)
241 ;; make sure bbdb does not create entries for messages while spam 232 ;; make sure bbdb does not create entries for messages while spam
242 ;; filter is scanning the rmail file: 233 ;; filter is scanning the rmail file:
243 (bbdb/mail_auto_create_p nil) 234 (bbdb/mail_auto_create_p nil)
244 ) 235 ;; Other things may wish to know if we are running (nothing
245 236 ;; uses this at present).
246 ;; Other things may wish to know if we are running (nothing uses 237 (rsf-scanning-messages-now t))
247 ;; this at present).
248 (setq rsf-scanning-messages-now t)
249 (save-excursion 238 (save-excursion
239 ;; Narrow buffer to header of message and get Sender and
240 ;; Subject fields to be used below:
250 (save-restriction 241 (save-restriction
251 (setq this-is-a-spam-email nil) 242 (goto-char (rmail-msgbeg msg))
252 ;; Narrow buffer to header of message and get Sender and 243 (narrow-to-region (point) (progn (search-forward "\n\n") (point)))
253 ;; Subject fields to be used below: 244 (setq message-sender (mail-fetch-field "From"))
254 (save-restriction 245 (setq message-to (mail-fetch-field "To")
255 (goto-char (rmail-msgbeg msg)) 246 message-cc (mail-fetch-field "Cc")
256 (narrow-to-region (point) (progn (search-forward "\n\n") (point))) 247 message-recipients (or (and message-to message-cc
257 (setq message-sender (mail-fetch-field "From")) 248 (concat message-to ", " message-cc))
258 (setq message-recipients 249 message-to
259 (concat (mail-fetch-field "To") 250 message-cc))
260 (if (mail-fetch-field "Cc") 251 (setq message-subject (mail-fetch-field "Subject"))
261 (concat ", " (mail-fetch-field "Cc"))))) 252 (setq message-content-type (mail-fetch-field "Content-Type"))
262 (setq message-subject (mail-fetch-field "Subject")) 253 (setq message-spam-status (mail-fetch-field "X-Spam-Status")))
263 (setq message-content-type (mail-fetch-field "Content-Type")) 254 ;; Check for blind CC condition. Set vars such that while
264 (setq message-spam-status (mail-fetch-field "X-Spam-Status")) 255 ;; loop will be bypassed and spam condition will trigger.
265 ) 256 (and rsf-no-blind-cc
266 ;; Find number of spam-definition elements in the list 257 (null message-recipients)
267 ;; rsf-definitions-alist specified by user: 258 (setq exit-while-loop t
268 (setq num-spam-definition-elements (safe-length 259 maybe-spam '(t . t)))
269 rsf-definitions-alist)) 260 ;; Check white list, and likewise cause while loop bypass.
270 261 (and message-sender
271 ;;; do we want to ignore case in spam definitions: 262 (let ((white-list rsf-white-list)
272 (setq case-fold-search rsf-ignore-case) 263 (found nil))
273 264 (while (and (not found) white-list)
274 ;; Check for blind CC condition. Set vars such that while 265 (if (string-match (car white-list) message-sender)
275 ;; loop will be bypassed and spam condition will trigger 266 (setq found t)
276 (if (and rsf-no-blind-cc 267 (setq white-list (cdr white-list))))
277 (null message-recipients)) 268 found)
278 (setq exit-while-loop t 269 (setq exit-while-loop t
279 maybe-spam t 270 maybe-spam '(nil . nil)))
280 this-is-a-spam-email t)) 271 ;; Scan all elements of the list rsf-definitions-alist.
281 272 (while (and (< num-element num-spam-definition-elements)
282 ;; Check white list, and likewise cause while loop 273 (not exit-while-loop))
283 ;; bypass. 274 (let ((definition (nth num-element rsf-definitions-alist)))
284 (if (and message-sender 275 ;; Initialize car, which is set to t in one of two cases:
285 (let ((white-list rsf-white-list) 276 ;; (1) unspecified definition-elements are found in
286 (found nil)) 277 ;; rsf-definitions-alist, (2) empty field is found in the
287 (while (and (not found) white-list) 278 ;; message being scanned (e.g. empty subject, sender,
288 (if (string-match (car white-list) message-sender) 279 ;; recipients, etc). It is set to nil if a non-empty field
289 (setq found t) 280 ;; of the scanned message does not match a specified field
290 (setq white-list (cdr white-list)))) 281 ;; in rsf-definitions-alist.
291 found)) 282 ;; FIXME the car is never set to t?!
292 (setq exit-while-loop t 283
293 maybe-spam nil 284 ;; Initialize cdr to nil. This is set to t if one of the
294 this-is-a-spam-email nil)) 285 ;; spam definitions matches a field in the scanned message.
295 286 (setq maybe-spam (cons t nil))
296 ;; maybe-spam is in the car, this-is-a-spam-email in cdr, this 287
297 ;; simplifies the call to rsf-check-field 288 ;; Maybe the different fields should also be done in a
298 (setq maybe-spam (cons maybe-spam this-is-a-spam-email)) 289 ;; loop to make the whole thing more flexible.
299 290
300 ;; scan all elements of the list rsf-definitions-alist 291 ;; If sender field is not specified in message being
301 (while (and 292 ;; scanned, AND if "from" field does not appear in spam
302 (< num-element num-spam-definition-elements) 293 ;; definitions for this element, this may still be spam due
303 (not exit-while-loop)) 294 ;; to another element...
304 (let ((definition (nth num-element rsf-definitions-alist))) 295 (rsf-check-field 'from message-sender definition maybe-spam)
305 ;; Initialize maybe-spam which is set to t in one of two 296 ;; Next, if spam was not ruled out already, check recipients:
306 ;; cases: (1) unspecified definition-elements are found in 297 (rsf-check-field 'to message-recipients definition maybe-spam)
307 ;; rsf-definitions-alist, (2) empty field is found 298 ;; Next, if spam was not ruled out already, check subject:
308 ;; in the message being scanned (e.g. empty subject, 299 (rsf-check-field 'subject message-subject definition maybe-spam)
309 ;; sender, recipients, etc). The variable is set to nil 300 ;; Next, if spam was not ruled out already, check content-type:
310 ;; if a non empty field of the scanned message does not 301 (rsf-check-field 'content-type message-content-type
311 ;; match a specified field in 302 definition maybe-spam)
312 ;; rsf-definitions-alist. 303 ;; Next, if spam was not ruled out already, check contents:
313 304 ;; If contents field is not specified, this may still be
314 ;; initialize this-is-a-spam-email to nil. This variable 305 ;; spam due to another element...
315 ;; is set to t if one of the spam definitions matches a 306 (rsf-check-field 'contents
316 ;; field in the scanned message. 307 (buffer-substring-no-properties
317 (setq maybe-spam (cons t nil)) 308 (rmail-msgbeg msg) (rmail-msgend msg))
318 309 definition maybe-spam)
319 ;; start scanning incoming message: 310
320 ;;--------------------------------- 311 ;; Finally, check the X-Spam-Status header. You will typically
321 312 ;; look for the "Yes" string in this header field.
322 ;; Maybe the different fields should also be done in a 313 (rsf-check-field 'x-spam-status message-spam-status
323 ;; loop to make the whole thing more flexible 314 definition maybe-spam)
324 ;; if sender field is not specified in message being 315
325 ;; scanned, AND if "from" field does not appear in spam 316 ;; If the search in rsf-definitions-alist found
326 ;; definitions for this element, this may still be spam 317 ;; that this email is spam, output the email to the spam
327 ;; due to another element... 318 ;; rmail file, mark the email for deletion, leave the
328 (rsf-check-field 'from message-sender definition maybe-spam) 319 ;; while loop and return nil so that an rmail summary line
329 ;; next, if spam was not ruled out already, check recipients: 320 ;; wont be displayed for this message: (FIXME ?)
330 (rsf-check-field 'to message-recipients definition maybe-spam) 321 (if (and (car maybe-spam) (cdr maybe-spam))
331 ;; next, if spam was not ruled out already, check subject: 322 (setq exit-while-loop t)
332 (rsf-check-field 'subject message-subject definition maybe-spam) 323 ;; Else, spam was not yet found, proceed to next element
333 ;; next, if spam was not ruled out already, check content-type: 324 ;; in rsf-definitions-alist:
334 (rsf-check-field 'content-type message-content-type 325 (setq num-element (1+ num-element)))))
335 definition maybe-spam) 326
336 ;; next, if spam was not ruled out already, check 327 (if (and (car maybe-spam) (cdr maybe-spam))
337 ;; contents: if contents field is not specified, this may 328 ;; Temporarily set rmail-current-message in order to output
338 ;; still be spam due to another element... 329 ;; and delete the spam msg if needed:
339 (rsf-check-field 'contents 330 (let ((rmail-current-message msg) ; FIXME does this do anything?
340 (buffer-substring 331 (action (cdr (assq 'action
341 (rmail-msgbeg msg) (rmail-msgend msg)) 332 (nth num-element rsf-definitions-alist)))))
342 definition maybe-spam) 333 ;; Check action item in rsf-definitions-alist and do it.
343 334 (cond
344 ;; finally, check the X-Spam-Status header. You will typically 335 ((eq action 'output-and-delete)
345 ;; look for the "Yes" string in this header field 336 ;; FIXME the prompt to write a new file leaves the raw
346 (rsf-check-field 'x-spam-status message-spam-status 337 ;; mbox buffer visible.
347 definition maybe-spam) 338 (rmail-output rsf-file)
348 339 ;; Don't delete if automatic deletion after output is on.
349 ;; if the search in rsf-definitions-alist found 340 (or rmail-delete-after-output (rmail-delete-message)))
350 ;; that this email is spam, output the email to the spam 341 ((eq action 'delete-spam)
351 ;; rmail file, mark the email for deletion, leave the 342 (rmail-delete-message)))
352 ;; while loop and return nil so that an rmail summary line 343 (setq return-value nil))
353 ;; wont be displayed for this message: 344 (setq return-value t)))
354 (if (and (car maybe-spam) (cdr maybe-spam))
355 ;; found that this is spam, no need to look at the
356 ;; rest of the rsf-definitions-alist, exit
357 ;; loop:
358 (setq exit-while-loop t)
359 ;; else, spam was not yet found, increment number of
360 ;; element in rsf-definitions-alist and proceed
361 ;; to next element:
362 (setq num-element (+ num-element 1)))
363 )
364 )
365
366 ;; (BK) re-set originally used variables
367 (setq this-is-a-spam-email (cdr maybe-spam)
368 maybe-spam (car maybe-spam))
369
370 (if (and this-is-a-spam-email maybe-spam)
371 (progn
372 ;;(message "Found spam!")
373 ;;(ding 1) (sleep-for 2)
374
375 ;; temprarily set rmail-current-message in order to
376 ;; output and delete the spam msg if needed:
377 (setq save-current-msg rmail-current-message)
378 (setq rmail-current-message msg)
379 ;; check action item and rsf-definitions-alist
380 ;; and do it:
381 (cond
382 ((equal (cdr (assoc 'action
383 (nth num-element rsf-definitions-alist)))
384 'output-and-delete)
385 (progn
386 (rmail-output rsf-file)
387 ;; Don't delete if automatic deletion after output
388 ;; is turned on
389 (unless rmail-delete-after-output (rmail-delete-message))
390 ))
391 ((equal (cdr (assoc 'action
392 (nth num-element rsf-definitions-alist)))
393 'delete-spam)
394 (progn
395 (rmail-delete-message)
396 ))
397 )
398 (setq rmail-current-message save-current-msg)
399 ;; set return value. These lines must be last in the
400 ;; function, so that they will determine the value
401 ;; returned by rmail-spam-filter:
402 (setq return-value nil))
403 (setq return-value t))))
404 (setq case-fold-search saved-case-fold-search)
405 (setq rsf-scanning-messages-now nil)
406 return-value)) 345 return-value))
407
408 346
409 ;; define functions for interactively adding sender/subject of a 347 ;; define functions for interactively adding sender/subject of a
410 ;; specific message to the spam definitions while reading it, using 348 ;; specific message to the spam definitions while reading it, using
411 ;; the menubar: 349 ;; the menubar:
412 (defun rsf-add-subject-to-spam-list () 350 (defun rsf-add-subject-to-spam-list ()
413 (interactive) 351 "Add the \"Subject\" header to the spam list."
414 (set-buffer rmail-buffer) 352 (interactive)
415 (let ((message-subject)) 353 (let ((message-subject (regexp-quote (rmail-get-header "Subject"))))
416 (setq message-subject (mail-fetch-field "Subject")) 354 ;; Note the use of a backquote and comma on the subject line here,
417 ;; note the use of a backquote and comma on the subject line here,
418 ;; to make sure message-subject is actually evaluated and its value 355 ;; to make sure message-subject is actually evaluated and its value
419 ;; substituted: 356 ;; substituted.
420 (add-to-list 'rsf-definitions-alist 357 (add-to-list 'rsf-definitions-alist
358 ;; Note that an empty elment is treated the same as
359 ;; an absent one, so why does it bother to add them?
421 (list '(from . "") 360 (list '(from . "")
422 '(to . "") 361 '(to . "")
423 `(subject . ,message-subject) 362 `(subject . ,message-subject)
424 '(content-type . "") 363 '(content-type . "")
425 '(contents . "") 364 '(contents . "")
427 t) 366 t)
428 (customize-mark-to-save 'rsf-definitions-alist) 367 (customize-mark-to-save 'rsf-definitions-alist)
429 (if rsf-autosave-newly-added-definitions 368 (if rsf-autosave-newly-added-definitions
430 (progn 369 (progn
431 (custom-save-all) 370 (custom-save-all)
432 (message "%s" (concat "added subject \n <<< \n" message-subject 371 (message "Added subject `%s' to spam list, and saved it"
433 " \n >>> \n to list of spam definitions. \n" 372 message-subject))
434 "and saved the spam definitions to file."))) 373 (message "Added subject `%s' to spam list (remember to save it)"
435 (message "%s" (concat "added subject \n <<< \n" message-subject 374 message-subject))))
436 " \n >>> \n to list of spam definitions. \n"
437 "Don't forget to save the spam definitions to file using the spam
438 menu"))
439 )))
440 375
441 (defun rsf-add-sender-to-spam-list () 376 (defun rsf-add-sender-to-spam-list ()
442 (interactive) 377 "Add the \"From\" address to the spam list."
443 (set-buffer rmail-buffer) 378 (interactive)
444 (let ((message-sender)) 379 (let ((message-sender (regexp-quote (rmail-get-header "From"))))
445 (setq message-sender (mail-fetch-field "From"))
446 ;; note the use of a backquote and comma on the "from" line here,
447 ;; to make sure message-sender is actually evaluated and its value
448 ;; substituted:
449 (add-to-list 'rsf-definitions-alist 380 (add-to-list 'rsf-definitions-alist
450 (list `(from . ,message-sender) 381 (list `(from . ,message-sender)
451 '(to . "") 382 '(to . "")
452 '(subject . "") 383 '(subject . "")
453 '(content-type . "") 384 '(content-type . "")
456 t) 387 t)
457 (customize-mark-to-save 'rsf-definitions-alist) 388 (customize-mark-to-save 'rsf-definitions-alist)
458 (if rsf-autosave-newly-added-definitions 389 (if rsf-autosave-newly-added-definitions
459 (progn 390 (progn
460 (custom-save-all) 391 (custom-save-all)
461 (message "%s" (concat "added sender \n <<< \n" message-sender 392 (message "Added sender `%s' to spam list, and saved it"
462 " \n >>> \n to list of spam definitions. \n" 393 message-sender))
463 "and saved the spam definitions to file."))) 394 (message "Added sender `%s' to spam list (remember to save it)"
464 (message "%s" (concat "added sender \n <<< \n " message-sender 395 message-sender))))
465 " \n >>> \n to list of spam definitions."
466 "Don't forget to save the spam definitions to file using the spam
467 menu"))
468 )))
469
470 396
471 (defun rsf-add-region-to-spam-list () 397 (defun rsf-add-region-to-spam-list ()
472 "Add the region makred by user in the rmail buffer to spam list. 398 "Add the marked region in the Rmail buffer to the spam list.
473 Added to spam definitions as a contents field." 399 Adds to spam definitions as a \"contents\" field."
474 (interactive) 400 (interactive)
475 (set-buffer rmail-buffer) 401 (set-buffer rmail-buffer)
476 (let ((region-to-spam-list)) 402 ;; Check if region is inactive or has zero size.
477 ;; check if region is inactive or has zero size: 403 (if (not (and mark-active (not (= (region-beginning) (region-end)))))
478 (if (not (and mark-active (not (= (region-beginning) (region-end))))) 404 ;; If inactive, print error message.
479 ;; if inactive, print error message: 405 (message "You must highlight some text in the Rmail buffer")
480 (message "you need to first highlight some text in the rmail buffer") 406 (if (< (- (region-end) (region-beginning)) rsf-min-region-to-spam-list)
481 (if (< (- (region-end) (region-beginning)) rsf-min-region-to-spam-list) 407 (message "Region is too small (minimum %d characters)"
482 (message 408 rsf-min-region-to-spam-list)
483 (concat "highlighted region is too small; min length set by variable \n" 409 ;; If region active and long enough, add to list of spam definitions.
484 "rsf-min-region-to-spam-list" 410 (let ((region-to-spam-list (regexp-quote
485 " is " (number-to-string rsf-min-region-to-spam-list))) 411 (buffer-substring-no-properties
486 ;; if region active and long enough, add to list of spam definisions: 412 (region-beginning) (region-end)))))
487 (progn 413 (add-to-list 'rsf-definitions-alist
488 (setq region-to-spam-list (buffer-substring (region-beginning) (region-end))) 414 (list '(from . "")
489 ;; note the use of a backquote and comma on the "from" line here, 415 '(to . "")
490 ;; to make sure message-sender is actually evaluated and its value 416 '(subject . "")
491 ;; substituted: 417 '(content-type . "")
492 (add-to-list 'rsf-definitions-alist 418 `(contents . ,region-to-spam-list)
493 (list '(from . "") 419 '(action . output-and-delete))
494 '(to . "") 420 t)
495 '(subject . "") 421 (customize-mark-to-save 'rsf-definitions-alist)
496 '(content-type . "") 422 (if rsf-autosave-newly-added-definitions
497 `(contents . ,region-to-spam-list) 423 (progn
498 '(action . output-and-delete)) 424 (custom-save-all)
499 t) 425 (message "Added highlighted text:\n%s\n\
500 (customize-mark-to-save 'rsf-definitions-alist) 426 to the spam list, and saved it" region-to-spam-list))
501 (if rsf-autosave-newly-added-definitions 427 (message "Added highlighted text:\n%s\n\
502 (progn 428 to the spam list (remember to save it)" region-to-spam-list))))))
503 (custom-save-all)
504 (message "%s" (concat "added highlighted text \n <<< \n" region-to-spam-list
505 " \n >>> \n to list of spam definitions. \n"
506 "and saved the spam definitions to file.")))
507 (message "%s" (concat "added highlighted text \n <<< \n " region-to-spam-list
508 " \n >>> \n to list of spam definitions."
509 "Don't forget to save the spam definitions to file using the
510 spam menu"))
511 ))))))
512
513 429
514 (defun rsf-customize-spam-definitions () 430 (defun rsf-customize-spam-definitions ()
515 (interactive) 431 "Customize `rsf-definitions-alist'."
516 (customize-variable (quote rsf-definitions-alist))) 432 (interactive)
433 (customize-variable 'rsf-definitions-alist))
517 434
518 (defun rsf-customize-group () 435 (defun rsf-customize-group ()
519 (interactive) 436 "Customize the rmail-spam-filter group."
520 (customize-group (quote rmail-spam-filter))) 437 (interactive)
438 (customize-group 'rmail-spam-filter))
521 439
522 (defun rsf-custom-save-all () 440 (defun rsf-custom-save-all ()
441 "Interactive version of `custom-save-all'."
523 (interactive) 442 (interactive)
524 (custom-save-all)) 443 (custom-save-all))
525 444
526 ;; add the actual menu items and keyboard shortcuts to both rmail and 445 ;; Add menu items (and keyboard shortcuts) to both rmail and rmail-summary.
527 ;; rmail-summary menu-bars:: 446 (dolist (map (list rmail-summary-mode-map rmail-mode-map))
528 (define-key rmail-summary-mode-map [menu-bar spam] 447 (easy-menu-define nil map nil
529 (cons "Spam" (make-sparse-keymap "Spam"))) 448 '("Spam"
530 (define-key rmail-mode-map [menu-bar spam] 449 ["Add subject to spam list" rsf-add-subject-to-spam-list]
531 (cons "Spam" (make-sparse-keymap "Spam"))) 450 ["Add sender to spam list" rsf-add-sender-to-spam-list]
532 451 ["Add region to spam list" rsf-add-region-to-spam-list]
533 (define-key rmail-summary-mode-map [menu-bar spam customize-group] 452 ["Save spam definitions" rsf-custom-save-all]
534 '("Browse customizations of rmail spam filter" . rsf-customize-group)) 453 "--"
535 (define-key rmail-mode-map [menu-bar spam customize-group] 454 ["Customize spam definitions" rsf-customize-spam-definitions]
536 '("Browse customizations of rmail spam filter" . rsf-customize-group)) 455 ["Browse spam customizations" rsf-customize-group]
537 (define-key rmail-summary-mode-map "\C-cSg" 'rsf-customize-group) 456 ))
538 (define-key rmail-mode-map "\C-cSg" 'rsf-customize-group) 457 (define-key map "\C-cSt" 'rsf-add-subject-to-spam-list)
539 458 (define-key map "\C-cSr" 'rsf-add-sender-to-spam-list)
540 (define-key rmail-summary-mode-map [menu-bar spam customize-spam-list] 459 (define-key map "\C-cSn" 'rsf-add-region-to-spam-list)
541 '("Customize list of spam definitions" . rsf-customize-spam-definitions)) 460 (define-key map "\C-cSa" 'rsf-custom-save-all)
542 (define-key rmail-mode-map [menu-bar spam customize-spam-list] 461 (define-key map "\C-cSd" 'rsf-customize-spam-definitions)
543 '("Customize list of spam definitions" . rsf-customize-spam-definitions)) 462 (define-key map "\C-cSg" 'rsf-customize-group))
544 (define-key rmail-summary-mode-map "\C-cSd" 'rsf-customize-spam-definitions)
545 (define-key rmail-mode-map "\C-cSd" 'rsf-customize-spam-definitions)
546
547 (define-key rmail-summary-mode-map [menu-bar spam lambda] '("----"))
548 (define-key rmail-mode-map [menu-bar spam lambda] '("----"))
549
550 (define-key rmail-summary-mode-map [menu-bar spam my-custom-save-all]
551 '("save newly added spam definitions to customization file" . rsf-custom-save-all))
552 (define-key rmail-mode-map [menu-bar spam my-custom-save-all]
553 '("save newly added spam definitions to customization file" . rsf-custom-save-all))
554 (define-key rmail-summary-mode-map "\C-cSa" 'rsf-custom-save-all)
555 (define-key rmail-mode-map "\C-cSa" 'rsf-custom-save-all)
556
557 (define-key rmail-summary-mode-map [menu-bar spam add-region-to-spam-list]
558 '("add region to spam list" . rsf-add-region-to-spam-list))
559 (define-key rmail-mode-map [menu-bar spam add-region-to-spam-list]
560 '("add region to spam list" . rsf-add-region-to-spam-list))
561 (define-key rmail-summary-mode-map "\C-cSn" 'rsf-add-region-to-spam-list)
562 (define-key rmail-mode-map "\C-cSn" 'rsf-add-region-to-spam-list)
563
564 (define-key rmail-summary-mode-map [menu-bar spam add-sender-to-spam-list]
565 '("add sender to spam list" . rsf-add-sender-to-spam-list))
566 (define-key rmail-mode-map [menu-bar spam add-sender-to-spam-list]
567 '("add sender to spam list" . rsf-add-sender-to-spam-list))
568 (define-key rmail-summary-mode-map "\C-cSr" 'rsf-add-sender-to-spam-list)
569 (define-key rmail-mode-map "\C-cSr" 'rsf-add-sender-to-spam-list)
570
571 (define-key rmail-summary-mode-map [menu-bar spam add-subject-to-spam-list]
572 '("add subject to spam list" . rsf-add-subject-to-spam-list))
573 (define-key rmail-mode-map [menu-bar spam add-subject-to-spam-list]
574 '("add subject to spam list" . rsf-add-subject-to-spam-list))
575 (define-key rmail-summary-mode-map "\C-cSt" 'rsf-add-subject-to-spam-list)
576 (define-key rmail-mode-map "\C-cSt" 'rsf-add-subject-to-spam-list)
577 463
578 (defun rsf-add-content-type-field () 464 (defun rsf-add-content-type-field ()
579 "Maintain backward compatibility for `rmail-spam-filter'. 465 "Maintain backward compatibility for `rmail-spam-filter'.
580 The most recent version of `rmail-spam-filter' checks the contents 466 The most recent version of `rmail-spam-filter' checks the content-type
581 field of the incoming mail to see if it spam. The format of 467 field of the incoming mail to see if it is spam. The format of
582 `rsf-definitions-alist' has therefore changed. This function 468 `rsf-definitions-alist' has therefore changed. This function
583 checks to see if old format is used, and if it is, it converts 469 checks to see if the old format is used, and updates it if necessary."
584 `rsf-definitions-alist' to the new format. Invoked
585 automatically, no user input is required."
586 (interactive) 470 (interactive)
587 (if (and rsf-definitions-alist 471 (if (and rsf-definitions-alist
588 (not (assoc 'content-type (car rsf-definitions-alist)))) 472 (not (assoc 'content-type (car rsf-definitions-alist))))
589 (let ((result nil) 473 (let ((result nil)
590 (current nil) 474 (current nil)
604 (setq rsf-definitions-alist result) 488 (setq rsf-definitions-alist result)
605 (customize-mark-to-save 'rsf-definitions-alist) 489 (customize-mark-to-save 'rsf-definitions-alist)
606 (if rsf-autosave-newly-added-definitions 490 (if rsf-autosave-newly-added-definitions
607 (progn 491 (progn
608 (custom-save-all) 492 (custom-save-all)
609 (message (concat "converted spam definitions to new format\n" 493 (message "Spam definitions converted to new format, and saved"))
610 "and saved the spam definitions to file."))) 494 (message "Spam definitions converted to new format (remember to save)")))))
611 (message (concat "converted spam definitions to new format\n"
612 "Don't forget to save the spam definitions to file using the
613 spam menu"))
614 ))))
615 495
616 (provide 'rmail-spam-filter) 496 (provide 'rmail-spam-filter)
617 497
618 ;; arch-tag: 03e1d45d-b72f-4dd7-8f04-e7fd78249746 498 ;; arch-tag: 03e1d45d-b72f-4dd7-8f04-e7fd78249746
619 ;;; rmail-spam-fitler ends here 499 ;;; rmail-spam-fitler ends here