Mercurial > emacs
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 |